1use std::io::{BufWriter, Write as _};
27
28use crate::cell::CellValue;
29use crate::error::{Error, Result};
30use crate::utils::cell_ref::cell_name_to_coordinates;
31use crate::utils::constants::{MAX_COLUMNS, MAX_ROWS, MAX_ROW_HEIGHT};
32
33use sheetkit_xml::worksheet::{Col, Cols, MergeCell, MergeCells, Pane, SheetView, SheetViews};
34
35const MAX_OUTLINE_LEVEL: u8 = 7;
37
38#[derive(Debug, Clone, Default)]
40pub struct StreamRowOptions {
41 pub height: Option<f64>,
43 pub visible: Option<bool>,
45 pub outline_level: Option<u8>,
47 pub style_id: Option<u32>,
49}
50
51#[derive(Debug, Clone, Default)]
53struct StreamColOptions {
54 width: Option<f64>,
55 style_id: Option<u32>,
56 hidden: Option<bool>,
57 outline_level: Option<u8>,
58}
59
60pub struct StreamedSheetData {
65 pub(crate) temp_file: tempfile::NamedTempFile,
67 #[allow(dead_code)]
69 pub(crate) data_len: u64,
70 pub(crate) sheet_views_xml: Option<String>,
72 pub(crate) cols_xml: Option<String>,
74 pub(crate) merge_cells_xml: Option<String>,
76}
77
78impl StreamedSheetData {
79 pub(crate) fn try_clone(&self) -> Result<Self> {
82 let mut new_temp = tempfile::NamedTempFile::new()?;
83 let mut src = std::fs::File::open(self.temp_file.path())?;
84 std::io::copy(&mut src, new_temp.as_file_mut())?;
85 Ok(StreamedSheetData {
86 temp_file: new_temp,
87 data_len: self.data_len,
88 sheet_views_xml: self.sheet_views_xml.clone(),
89 cols_xml: self.cols_xml.clone(),
90 merge_cells_xml: self.merge_cells_xml.clone(),
91 })
92 }
93}
94
95pub struct StreamWriter {
101 sheet_name: String,
102 writer: BufWriter<tempfile::NamedTempFile>,
103 bytes_written: u64,
104 last_row: u32,
105 started: bool,
106 finished: bool,
107 col_widths: Vec<(u32, u32, f64)>,
108 col_options: Vec<(u32, StreamColOptions)>,
109 merge_cells: Vec<String>,
110 freeze_pane: Option<(u32, u32, String)>,
112}
113
114unsafe impl Send for StreamWriter {}
118
119impl std::fmt::Debug for StreamWriter {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 f.debug_struct("StreamWriter")
122 .field("sheet_name", &self.sheet_name)
123 .field("bytes_written", &self.bytes_written)
124 .field("last_row", &self.last_row)
125 .field("started", &self.started)
126 .field("finished", &self.finished)
127 .finish()
128 }
129}
130
131impl StreamWriter {
132 pub fn new(sheet_name: &str) -> Self {
134 let temp = tempfile::NamedTempFile::new().expect("failed to create temp file");
135 Self {
136 sheet_name: sheet_name.to_string(),
137 writer: BufWriter::new(temp),
138 bytes_written: 0,
139 last_row: 0,
140 started: false,
141 finished: false,
142 col_widths: Vec::new(),
143 col_options: Vec::new(),
144 merge_cells: Vec::new(),
145 freeze_pane: None,
146 }
147 }
148
149 pub fn sheet_name(&self) -> &str {
151 &self.sheet_name
152 }
153
154 pub fn set_col_width(&mut self, col: u32, width: f64) -> Result<()> {
157 self.set_col_width_range(col, col, width)
158 }
159
160 pub fn set_col_width_range(&mut self, min_col: u32, max_col: u32, width: f64) -> Result<()> {
163 if self.finished {
164 return Err(Error::StreamAlreadyFinished);
165 }
166 if self.started {
167 return Err(Error::StreamColumnsAfterRows);
168 }
169 if min_col == 0 || max_col == 0 || min_col > MAX_COLUMNS || max_col > MAX_COLUMNS {
170 return Err(Error::InvalidColumnNumber(if min_col == 0 {
171 min_col
172 } else {
173 max_col
174 }));
175 }
176 self.col_widths.push((min_col, max_col, width));
177 Ok(())
178 }
179
180 pub fn set_col_style(&mut self, col: u32, style_id: u32) -> Result<()> {
183 self.ensure_col_configurable(col)?;
184 self.get_or_create_col_options(col).style_id = Some(style_id);
185 Ok(())
186 }
187
188 pub fn set_col_visible(&mut self, col: u32, visible: bool) -> Result<()> {
191 self.ensure_col_configurable(col)?;
192 self.get_or_create_col_options(col).hidden = Some(!visible);
193 Ok(())
194 }
195
196 pub fn set_col_outline_level(&mut self, col: u32, level: u8) -> Result<()> {
199 self.ensure_col_configurable(col)?;
200 if level > MAX_OUTLINE_LEVEL {
201 return Err(Error::OutlineLevelExceeded {
202 level,
203 max: MAX_OUTLINE_LEVEL,
204 });
205 }
206 self.get_or_create_col_options(col).outline_level = Some(level);
207 Ok(())
208 }
209
210 pub fn set_freeze_panes(&mut self, top_left_cell: &str) -> Result<()> {
216 if self.finished {
217 return Err(Error::StreamAlreadyFinished);
218 }
219 if self.started {
220 return Err(Error::StreamColumnsAfterRows);
221 }
222 let (col, row) = cell_name_to_coordinates(top_left_cell)?;
223 if col == 1 && row == 1 {
224 return Err(Error::InvalidCellReference(
225 "freeze pane at A1 has no effect".to_string(),
226 ));
227 }
228 self.freeze_pane = Some((col - 1, row - 1, top_left_cell.to_string()));
229 Ok(())
230 }
231
232 pub fn write_row_with_options(
234 &mut self,
235 row: u32,
236 values: &[CellValue],
237 options: &StreamRowOptions,
238 ) -> Result<()> {
239 self.write_row_impl(row, values, None, Some(options))
240 }
241
242 pub fn write_row(&mut self, row: u32, values: &[CellValue]) -> Result<()> {
245 self.write_row_impl(row, values, None, None)
246 }
247
248 pub fn write_rows(&mut self, start_row: u32, rows: &[Vec<CellValue>]) -> Result<()> {
253 for (i, values) in rows.iter().enumerate() {
254 let row_num = start_row
255 .checked_add(i as u32)
256 .ok_or(Error::InvalidRowNumber(u32::MAX))?;
257 self.write_row_impl(row_num, values, None, None)?;
258 }
259 Ok(())
260 }
261
262 pub fn write_row_with_style(
264 &mut self,
265 row: u32,
266 values: &[CellValue],
267 style_id: u32,
268 ) -> Result<()> {
269 self.write_row_impl(row, values, Some(style_id), None)
270 }
271
272 pub fn add_merge_cell(&mut self, reference: &str) -> Result<()> {
276 if self.finished {
277 return Err(Error::StreamAlreadyFinished);
278 }
279 let parts: Vec<&str> = reference.split(':').collect();
281 if parts.len() != 2 {
282 return Err(Error::InvalidMergeCellReference(reference.to_string()));
283 }
284 cell_name_to_coordinates(parts[0])
285 .map_err(|_| Error::InvalidMergeCellReference(reference.to_string()))?;
286 cell_name_to_coordinates(parts[1])
287 .map_err(|_| Error::InvalidMergeCellReference(reference.to_string()))?;
288 self.merge_cells.push(reference.to_string());
289 Ok(())
290 }
291
292 pub fn into_streamed_data(mut self) -> Result<(String, StreamedSheetData)> {
295 if self.finished {
296 return Err(Error::StreamAlreadyFinished);
297 }
298 self.finished = true;
299
300 let sheet_views_xml = self.build_sheet_views_xml();
302 let cols_xml = self.build_cols_xml();
303 let merge_cells_xml = self.build_merge_cells_xml();
304 let bytes_written = self.bytes_written;
305 let sheet_name = self.sheet_name.clone();
306
307 self.writer.flush()?;
309 let temp_file = self
310 .writer
311 .into_inner()
312 .map_err(|e| Error::Io(e.into_error()))?;
313
314 let data = StreamedSheetData {
315 temp_file,
316 data_len: bytes_written,
317 sheet_views_xml,
318 cols_xml,
319 merge_cells_xml,
320 };
321 Ok((sheet_name, data))
322 }
323
324 fn ensure_col_configurable(&self, col: u32) -> Result<()> {
327 if self.finished {
328 return Err(Error::StreamAlreadyFinished);
329 }
330 if self.started {
331 return Err(Error::StreamColumnsAfterRows);
332 }
333 if col == 0 || col > MAX_COLUMNS {
334 return Err(Error::InvalidColumnNumber(col));
335 }
336 Ok(())
337 }
338
339 fn get_or_create_col_options(&mut self, col: u32) -> &mut StreamColOptions {
341 if let Some(pos) = self.col_options.iter().position(|(c, _)| *c == col) {
342 &mut self.col_options[pos].1
343 } else {
344 self.col_options.push((col, StreamColOptions::default()));
345 let last = self.col_options.len() - 1;
346 &mut self.col_options[last].1
347 }
348 }
349
350 fn build_sheet_views_xml(&self) -> Option<String> {
352 let (x_split, y_split, top_left_cell) = self.freeze_pane.as_ref()?;
353 let active_pane = match (*x_split > 0, *y_split > 0) {
354 (true, true) => "bottomRight",
355 (true, false) => "topRight",
356 (false, true) => "bottomLeft",
357 (false, false) => unreachable!(),
358 };
359
360 let sheet_views = SheetViews {
361 sheet_views: vec![SheetView {
362 tab_selected: Some(true),
363 show_grid_lines: None,
364 show_formulas: None,
365 show_row_col_headers: None,
366 zoom_scale: None,
367 view: None,
368 top_left_cell: None,
369 workbook_view_id: 0,
370 pane: Some(Pane {
371 x_split: if *x_split > 0 { Some(*x_split) } else { None },
372 y_split: if *y_split > 0 { Some(*y_split) } else { None },
373 top_left_cell: Some(top_left_cell.clone()),
374 active_pane: Some(active_pane.to_string()),
375 state: Some("frozen".to_string()),
376 }),
377 selection: vec![],
378 }],
379 };
380
381 quick_xml::se::to_string_with_root("sheetViews", &sheet_views).ok()
382 }
383
384 fn build_cols_xml(&self) -> Option<String> {
386 let has_widths = !self.col_widths.is_empty();
387 let has_options = !self.col_options.is_empty();
388 if !has_widths && !has_options {
389 return None;
390 }
391
392 let mut col_defs = Vec::new();
393 for &(min, max, width) in &self.col_widths {
394 col_defs.push(Col {
395 min,
396 max,
397 width: Some(width),
398 style: None,
399 hidden: None,
400 custom_width: Some(true),
401 outline_level: None,
402 });
403 }
404 for &(col_num, ref opts) in &self.col_options {
405 col_defs.push(Col {
406 min: col_num,
407 max: col_num,
408 width: opts.width,
409 style: opts.style_id,
410 hidden: opts.hidden,
411 custom_width: if opts.width.is_some() {
412 Some(true)
413 } else {
414 None
415 },
416 outline_level: opts.outline_level.filter(|&l| l > 0),
417 });
418 }
419
420 let cols = Cols { cols: col_defs };
421 quick_xml::se::to_string_with_root("cols", &cols).ok()
422 }
423
424 fn build_merge_cells_xml(&self) -> Option<String> {
426 if self.merge_cells.is_empty() {
427 return None;
428 }
429
430 let mc = MergeCells {
431 count: Some(self.merge_cells.len() as u32),
432 merge_cells: self
433 .merge_cells
434 .iter()
435 .map(|r| MergeCell {
436 reference: r.clone(),
437 })
438 .collect(),
439 cached_coords: Vec::new(),
440 };
441 quick_xml::se::to_string_with_root("mergeCells", &mc).ok()
442 }
443
444 fn write_row_impl(
447 &mut self,
448 row: u32,
449 values: &[CellValue],
450 cell_style_id: Option<u32>,
451 options: Option<&StreamRowOptions>,
452 ) -> Result<()> {
453 if self.finished {
454 return Err(Error::StreamAlreadyFinished);
455 }
456
457 if row == 0 || row > MAX_ROWS {
459 return Err(Error::InvalidRowNumber(row));
460 }
461
462 if row <= self.last_row {
464 return Err(Error::StreamRowAlreadyWritten { row });
465 }
466
467 if values.len() > MAX_COLUMNS as usize {
469 return Err(Error::InvalidColumnNumber(values.len() as u32));
470 }
471
472 if let Some(opts) = options {
474 if let Some(height) = opts.height {
475 if height > MAX_ROW_HEIGHT {
476 return Err(Error::RowHeightExceeded {
477 height,
478 max: MAX_ROW_HEIGHT,
479 });
480 }
481 }
482 if let Some(level) = opts.outline_level {
483 if level > MAX_OUTLINE_LEVEL {
484 return Err(Error::OutlineLevelExceeded {
485 level,
486 max: MAX_OUTLINE_LEVEL,
487 });
488 }
489 }
490 }
491
492 self.started = true;
493 self.last_row = row;
494
495 let xml = build_row_xml(row, values, cell_style_id, options);
497 let bytes = xml.as_bytes();
498 self.writer.write_all(bytes)?;
499 self.bytes_written += bytes.len() as u64;
500
501 Ok(())
502 }
503}
504
505pub(crate) fn build_worksheet_header(streamed: &StreamedSheetData) -> String {
508 let mut xml = String::with_capacity(512);
509 xml.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
510 xml.push('\n');
511 xml.push_str(
512 r#"<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">"#,
513 );
514
515 if let Some(ref views) = streamed.sheet_views_xml {
516 xml.push_str(views);
517 }
518 if let Some(ref cols) = streamed.cols_xml {
519 xml.push_str(cols);
520 }
521
522 xml.push_str("<sheetData>");
523 xml
524}
525
526pub(crate) fn build_worksheet_footer(streamed: &StreamedSheetData) -> String {
529 let mut xml = String::with_capacity(256);
530 xml.push_str("</sheetData>");
531
532 if let Some(ref mc) = streamed.merge_cells_xml {
533 xml.push_str(mc);
534 }
535
536 xml.push_str("</worksheet>");
537 xml
538}
539
540pub(crate) fn write_streamed_sheet<W: std::io::Write + std::io::Seek>(
544 zip: &mut zip::ZipWriter<W>,
545 entry_name: &str,
546 streamed: &StreamedSheetData,
547 options: zip::write::SimpleFileOptions,
548) -> Result<()> {
549 zip.start_file(entry_name, options)
550 .map_err(|e| Error::Zip(e.to_string()))?;
551
552 let header = build_worksheet_header(streamed);
554 zip.write_all(header.as_bytes())?;
555
556 let file = std::fs::File::open(streamed.temp_file.path())?;
558 let mut reader = std::io::BufReader::new(file);
559 std::io::copy(&mut reader, zip)?;
560
561 let footer = build_worksheet_footer(streamed);
563 zip.write_all(footer.as_bytes())?;
564
565 Ok(())
566}
567
568fn build_row_xml(
570 row: u32,
571 values: &[CellValue],
572 cell_style_id: Option<u32>,
573 options: Option<&StreamRowOptions>,
574) -> String {
575 let mut xml = String::with_capacity(128 + values.len() * 64);
576 xml.push_str("<row r=\"");
577 xml.push_str(&row.to_string());
578 xml.push('"');
579
580 if let Some(opts) = options {
582 if let Some(sid) = opts.style_id {
583 xml.push_str(" s=\"");
584 xml.push_str(&sid.to_string());
585 xml.push_str("\" customFormat=\"1\"");
586 }
587 if let Some(height) = opts.height {
588 xml.push_str(" ht=\"");
589 xml.push_str(&height.to_string());
590 xml.push_str("\" customHeight=\"1\"");
591 }
592 if let Some(false) = opts.visible {
593 xml.push_str(" hidden=\"1\"");
594 }
595 if let Some(level) = opts.outline_level {
596 if level > 0 {
597 xml.push_str(" outlineLevel=\"");
598 xml.push_str(&level.to_string());
599 xml.push('"');
600 }
601 }
602 }
603
604 xml.push('>');
605
606 for (i, value) in values.iter().enumerate() {
607 let col = (i as u32) + 1;
608 if matches!(value, CellValue::Empty) {
609 continue;
610 }
611
612 let cell_ref = crate::utils::cell_ref::coordinates_to_cell_name(col, row)
613 .unwrap_or_else(|_| format!("{col}:{row}"));
614
615 xml.push_str("<c r=\"");
616 xml.push_str(&cell_ref);
617 xml.push('"');
618
619 if let Some(sid) = cell_style_id {
621 xml.push_str(" s=\"");
622 xml.push_str(&sid.to_string());
623 xml.push('"');
624 }
625
626 match value {
627 CellValue::String(s) => {
628 xml.push_str(" t=\"inlineStr\"><is><t>");
629 xml_escape_into(&mut xml, s);
630 xml.push_str("</t></is></c>");
631 }
632 CellValue::Number(n) => {
633 xml.push_str("><v>");
634 xml.push_str(&n.to_string());
635 xml.push_str("</v></c>");
636 }
637 CellValue::Date(serial) => {
638 xml.push_str("><v>");
639 xml.push_str(&serial.to_string());
640 xml.push_str("</v></c>");
641 }
642 CellValue::Bool(b) => {
643 xml.push_str(" t=\"b\"><v>");
644 xml.push_str(if *b { "1" } else { "0" });
645 xml.push_str("</v></c>");
646 }
647 CellValue::Formula { expr, result } => {
648 if let Some(res) = result {
652 match res.as_ref() {
653 CellValue::String(_) => xml.push_str(" t=\"str\""),
654 CellValue::Bool(_) => xml.push_str(" t=\"b\""),
655 CellValue::Error(_) => xml.push_str(" t=\"e\""),
656 _ => {} }
658 }
659 xml.push_str("><f>");
660 xml_escape_into(&mut xml, expr);
661 xml.push_str("</f>");
662 if let Some(res) = result {
663 match res.as_ref() {
664 CellValue::String(s) => {
665 xml.push_str("<v>");
666 xml_escape_into(&mut xml, s);
667 xml.push_str("</v>");
668 }
669 CellValue::Number(n) => {
670 xml.push_str("<v>");
671 xml.push_str(&n.to_string());
672 xml.push_str("</v>");
673 }
674 CellValue::Bool(b) => {
675 xml.push_str("<v>");
676 xml.push_str(if *b { "1" } else { "0" });
677 xml.push_str("</v>");
678 }
679 CellValue::Date(d) => {
680 xml.push_str("<v>");
681 xml.push_str(&d.to_string());
682 xml.push_str("</v>");
683 }
684 CellValue::Error(e) => {
685 xml.push_str("<v>");
686 xml_escape_into(&mut xml, e);
687 xml.push_str("</v>");
688 }
689 _ => {}
690 }
691 }
692 xml.push_str("</c>");
693 }
694 CellValue::Error(e) => {
695 xml.push_str(" t=\"e\"><v>");
696 xml_escape_into(&mut xml, e);
697 xml.push_str("</v></c>");
698 }
699 CellValue::RichString(runs) => {
700 let plain = crate::rich_text::rich_text_to_plain(runs);
701 xml.push_str(" t=\"inlineStr\"><is><t>");
702 xml_escape_into(&mut xml, &plain);
703 xml.push_str("</t></is></c>");
704 }
705 CellValue::Empty => unreachable!(),
706 }
707 }
708
709 xml.push_str("</row>");
710 xml
711}
712
713fn xml_escape_into(buf: &mut String, s: &str) {
715 for ch in s.chars() {
716 match ch {
717 '&' => buf.push_str("&"),
718 '<' => buf.push_str("<"),
719 '>' => buf.push_str(">"),
720 '"' => buf.push_str("""),
721 '\'' => buf.push_str("'"),
722 _ => buf.push(ch),
723 }
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730 use sheetkit_xml::worksheet::WorksheetXml;
731 use std::io::{Read as _, Seek as _};
732
733 fn finish_and_parse(sw: StreamWriter) -> WorksheetXml {
735 let (_, mut streamed) = sw.into_streamed_data().unwrap();
736 let header = build_worksheet_header(&streamed);
737 let footer = build_worksheet_footer(&streamed);
738
739 let file = streamed.temp_file.as_file_mut();
740 file.seek(std::io::SeekFrom::Start(0)).unwrap();
741 let mut row_data = String::new();
742 file.read_to_string(&mut row_data).unwrap();
743
744 let full_xml = format!("{header}{row_data}{footer}");
745 quick_xml::de::from_str(&full_xml).unwrap()
746 }
747
748 fn finish_and_get_xml(sw: StreamWriter) -> String {
750 let (_, mut streamed) = sw.into_streamed_data().unwrap();
751 let header = build_worksheet_header(&streamed);
752 let footer = build_worksheet_footer(&streamed);
753
754 let file = streamed.temp_file.as_file_mut();
755 file.seek(std::io::SeekFrom::Start(0)).unwrap();
756 let mut row_data = String::new();
757 file.read_to_string(&mut row_data).unwrap();
758
759 format!("{header}{row_data}{footer}")
760 }
761
762 #[test]
763 fn test_basic_write_and_finish() {
764 let mut sw = StreamWriter::new("Sheet1");
765 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
766 .unwrap();
767 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
768 .unwrap();
769 let xml = finish_and_get_xml(sw);
770
771 assert!(xml.contains("<?xml version=\"1.0\""));
772 assert!(xml.contains("<worksheet"));
773 assert!(xml.contains("<sheetData>"));
774 assert!(xml.contains("</sheetData>"));
775 assert!(xml.contains("</worksheet>"));
776 }
777
778 #[test]
779 fn test_parse_output_xml_back() {
780 let mut sw = StreamWriter::new("TestSheet");
781 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
782 .unwrap();
783 let ws = finish_and_parse(sw);
784
785 assert_eq!(ws.sheet_data.rows.len(), 1);
786 assert_eq!(ws.sheet_data.rows[0].r, 1);
787 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
788 assert_eq!(
790 ws.sheet_data.rows[0].cells[0].t,
791 sheetkit_xml::worksheet::CellTypeTag::InlineString
792 );
793 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
794 assert_eq!(
796 ws.sheet_data.rows[0].cells[1].t,
797 sheetkit_xml::worksheet::CellTypeTag::None
798 );
799 assert_eq!(ws.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
800 assert_eq!(ws.sheet_data.rows[0].cells[1].r, "B1");
801 }
802
803 #[test]
804 fn test_inline_strings_used() {
805 let mut sw = StreamWriter::new("Sheet1");
806 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from("World")])
807 .unwrap();
808 let xml = finish_and_get_xml(sw);
809
810 assert!(xml.contains("t=\"inlineStr\""));
812 assert!(xml.contains("<is><t>Hello</t></is>"));
813 assert!(xml.contains("<is><t>World</t></is>"));
814 assert!(!xml.contains("t=\"s\""));
816 }
817
818 #[test]
819 fn test_number_value() {
820 let mut sw = StreamWriter::new("Sheet1");
821 sw.write_row(1, &[CellValue::from(3.15)]).unwrap();
822 let xml = finish_and_get_xml(sw);
823
824 assert!(xml.contains("<v>3.15</v>"));
825 }
826
827 #[test]
828 fn test_bool_values() {
829 let mut sw = StreamWriter::new("Sheet1");
830 sw.write_row(1, &[CellValue::from(true), CellValue::from(false)])
831 .unwrap();
832 let xml = finish_and_get_xml(sw);
833
834 assert!(xml.contains("<v>1</v>"));
835 assert!(xml.contains("<v>0</v>"));
836 }
837
838 #[test]
839 fn test_formula_value() {
840 let mut sw = StreamWriter::new("Sheet1");
841 sw.write_row(
842 1,
843 &[CellValue::Formula {
844 expr: "SUM(A2:A10)".to_string(),
845 result: None,
846 }],
847 )
848 .unwrap();
849 let xml = finish_and_get_xml(sw);
850
851 assert!(xml.contains("SUM(A2:A10)"));
852 }
853
854 #[test]
855 fn test_error_value() {
856 let mut sw = StreamWriter::new("Sheet1");
857 sw.write_row(1, &[CellValue::Error("#DIV/0!".to_string())])
858 .unwrap();
859 let xml = finish_and_get_xml(sw);
860
861 assert!(xml.contains("#DIV/0!"));
862 }
863
864 #[test]
865 fn test_empty_values_are_skipped() {
866 let mut sw = StreamWriter::new("Sheet1");
867 sw.write_row(
868 1,
869 &[CellValue::from("A"), CellValue::Empty, CellValue::from("C")],
870 )
871 .unwrap();
872 let ws = finish_and_parse(sw);
873
874 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
876 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
877 assert_eq!(ws.sheet_data.rows[0].cells[1].r, "C1");
878 }
879
880 #[test]
881 fn test_write_row_with_style() {
882 let mut sw = StreamWriter::new("Sheet1");
883 sw.write_row_with_style(1, &[CellValue::from("Styled")], 5)
884 .unwrap();
885 let xml = finish_and_get_xml(sw);
886
887 assert!(xml.contains("s=\"5\""));
888 }
889
890 #[test]
891 fn test_set_col_width_before_rows() {
892 let mut sw = StreamWriter::new("Sheet1");
893 sw.set_col_width(1, 20.0).unwrap();
894 sw.write_row(1, &[CellValue::from("data")]).unwrap();
895 let ws = finish_and_parse(sw);
896
897 let cols = ws.cols.unwrap();
898 assert_eq!(cols.cols.len(), 1);
899 assert_eq!(cols.cols[0].min, 1);
900 assert_eq!(cols.cols[0].max, 1);
901 assert_eq!(cols.cols[0].width, Some(20.0));
902 assert_eq!(cols.cols[0].custom_width, Some(true));
903 }
904
905 #[test]
906 fn test_set_col_width_range() {
907 let mut sw = StreamWriter::new("Sheet1");
908 sw.set_col_width_range(1, 3, 15.5).unwrap();
909 let ws = finish_and_parse(sw);
910
911 let cols = ws.cols.unwrap();
912 assert_eq!(cols.cols.len(), 1);
913 assert_eq!(cols.cols[0].min, 1);
914 assert_eq!(cols.cols[0].max, 3);
915 assert_eq!(cols.cols[0].width, Some(15.5));
916 }
917
918 #[test]
919 fn test_col_width_in_output_xml() {
920 let mut sw = StreamWriter::new("Sheet1");
921 sw.set_col_width(2, 25.0).unwrap();
922 sw.write_row(1, &[CellValue::from("data")]).unwrap();
923 let xml = finish_and_get_xml(sw);
924
925 let cols_pos = xml.find("<cols>").unwrap();
927 let sheet_data_pos = xml.find("<sheetData").unwrap();
928 assert!(cols_pos < sheet_data_pos);
929 }
930
931 #[test]
932 fn test_col_width_after_rows_returns_error() {
933 let mut sw = StreamWriter::new("Sheet1");
934 sw.write_row(1, &[CellValue::from("data")]).unwrap();
935 let result = sw.set_col_width(1, 20.0);
936 assert!(result.is_err());
937 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
938 }
939
940 #[test]
941 fn test_rows_in_order_succeeds() {
942 let mut sw = StreamWriter::new("Sheet1");
943 sw.write_row(1, &[CellValue::from("a")]).unwrap();
944 sw.write_row(2, &[CellValue::from("b")]).unwrap();
945 sw.write_row(3, &[CellValue::from("c")]).unwrap();
946 let _data = sw.into_streamed_data().unwrap();
947 }
948
949 #[test]
950 fn test_rows_with_gaps_succeeds() {
951 let mut sw = StreamWriter::new("Sheet1");
952 sw.write_row(1, &[CellValue::from("a")]).unwrap();
953 sw.write_row(3, &[CellValue::from("b")]).unwrap();
954 sw.write_row(5, &[CellValue::from("c")]).unwrap();
955 let ws = finish_and_parse(sw);
956
957 assert_eq!(ws.sheet_data.rows.len(), 3);
958 assert_eq!(ws.sheet_data.rows[0].r, 1);
959 assert_eq!(ws.sheet_data.rows[1].r, 3);
960 assert_eq!(ws.sheet_data.rows[2].r, 5);
961 }
962
963 #[test]
964 fn test_duplicate_row_number_fails() {
965 let mut sw = StreamWriter::new("Sheet1");
966 sw.write_row(1, &[CellValue::from("a")]).unwrap();
967 let result = sw.write_row(1, &[CellValue::from("b")]);
968 assert!(result.is_err());
969 assert!(matches!(
970 result.unwrap_err(),
971 Error::StreamRowAlreadyWritten { row: 1 }
972 ));
973 }
974
975 #[test]
976 fn test_row_zero_fails() {
977 let mut sw = StreamWriter::new("Sheet1");
978 let result = sw.write_row(0, &[CellValue::from("a")]);
979 assert!(result.is_err());
980 assert!(matches!(result.unwrap_err(), Error::InvalidRowNumber(0)));
981 }
982
983 #[test]
984 fn test_row_out_of_order_fails() {
985 let mut sw = StreamWriter::new("Sheet1");
986 sw.write_row(5, &[CellValue::from("a")]).unwrap();
987 let result = sw.write_row(3, &[CellValue::from("b")]);
988 assert!(result.is_err());
989 assert!(matches!(
990 result.unwrap_err(),
991 Error::StreamRowAlreadyWritten { row: 3 }
992 ));
993 }
994
995 #[test]
996 fn test_merge_cells_in_output() {
997 let mut sw = StreamWriter::new("Sheet1");
998 sw.write_row(1, &[CellValue::from("Merged")]).unwrap();
999 sw.add_merge_cell("A1:B1").unwrap();
1000 let ws = finish_and_parse(sw);
1001
1002 let mc = ws.merge_cells.unwrap();
1003 assert_eq!(mc.merge_cells.len(), 1);
1004 assert_eq!(mc.merge_cells[0].reference, "A1:B1");
1005 }
1006
1007 #[test]
1008 fn test_multiple_merge_cells() {
1009 let mut sw = StreamWriter::new("Sheet1");
1010 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1011 sw.add_merge_cell("A1:B1").unwrap();
1012 sw.add_merge_cell("C1:D1").unwrap();
1013 let ws = finish_and_parse(sw);
1014
1015 let mc = ws.merge_cells.unwrap();
1016 assert_eq!(mc.merge_cells.len(), 2);
1017 assert_eq!(mc.merge_cells[0].reference, "A1:B1");
1018 assert_eq!(mc.merge_cells[1].reference, "C1:D1");
1019 }
1020
1021 #[test]
1022 fn test_finish_twice_returns_error() {
1023 let mut sw = StreamWriter::new("Sheet1");
1024 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1025 sw.into_streamed_data().unwrap();
1027 }
1029
1030 #[test]
1031 fn test_write_after_finish_returns_error() {
1032 let mut sw = StreamWriter::new("Sheet1");
1035 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1036 sw.finished = true;
1037 let result = sw.write_row(2, &[CellValue::from("b")]);
1038 assert!(result.is_err());
1039 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1040 }
1041
1042 #[test]
1043 fn test_finish_with_no_rows() {
1044 let sw = StreamWriter::new("Sheet1");
1045 let ws = finish_and_parse(sw);
1046
1047 assert!(ws.sheet_data.rows.is_empty());
1049 }
1050
1051 #[test]
1052 fn test_finish_with_cols_and_no_rows() {
1053 let mut sw = StreamWriter::new("Sheet1");
1054 sw.set_col_width(1, 20.0).unwrap();
1055 let ws = finish_and_parse(sw);
1056
1057 assert!(ws.cols.is_some());
1058 assert!(ws.sheet_data.rows.is_empty());
1059 }
1060
1061 #[test]
1062 fn test_add_merge_cell_invalid_reference() {
1063 let mut sw = StreamWriter::new("Sheet1");
1064 let result = sw.add_merge_cell("A1B2");
1066 assert!(result.is_err());
1067 assert!(matches!(
1068 result.unwrap_err(),
1069 Error::InvalidMergeCellReference(_)
1070 ));
1071 }
1072
1073 #[test]
1074 fn test_add_merge_cell_invalid_cell_name() {
1075 let mut sw = StreamWriter::new("Sheet1");
1076 let result = sw.add_merge_cell("ZZZ:B2");
1078 assert!(result.is_err());
1079 assert!(matches!(
1080 result.unwrap_err(),
1081 Error::InvalidMergeCellReference(_)
1082 ));
1083 }
1084
1085 #[test]
1086 fn test_add_merge_cell_empty_reference() {
1087 let mut sw = StreamWriter::new("Sheet1");
1088 let result = sw.add_merge_cell("");
1089 assert!(result.is_err());
1090 assert!(matches!(
1091 result.unwrap_err(),
1092 Error::InvalidMergeCellReference(_)
1093 ));
1094 }
1095
1096 #[test]
1097 fn test_add_merge_cell_after_finish_fails() {
1098 let mut sw = StreamWriter::new("Sheet1");
1099 sw.finished = true;
1100 let result = sw.add_merge_cell("A1:B1");
1101 assert!(result.is_err());
1102 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1103 }
1104
1105 #[test]
1106 fn test_set_col_width_after_finish_fails() {
1107 let mut sw = StreamWriter::new("Sheet1");
1108 sw.finished = true;
1109 let result = sw.set_col_width(1, 10.0);
1110 assert!(result.is_err());
1111 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1112 }
1113
1114 #[test]
1115 fn test_sheet_name_getter() {
1116 let sw = StreamWriter::new("MySheet");
1117 assert_eq!(sw.sheet_name(), "MySheet");
1118 }
1119
1120 #[test]
1121 fn test_all_value_types_in_single_row() {
1122 let mut sw = StreamWriter::new("Sheet1");
1123 sw.write_row(
1124 1,
1125 &[
1126 CellValue::from("text"),
1127 CellValue::from(42),
1128 CellValue::from(3.15),
1129 CellValue::from(true),
1130 CellValue::from(false),
1131 CellValue::Formula {
1132 expr: "A1+B1".to_string(),
1133 result: None,
1134 },
1135 CellValue::Error("#N/A".to_string()),
1136 CellValue::Empty,
1137 ],
1138 )
1139 .unwrap();
1140 let ws = finish_and_parse(sw);
1141
1142 assert_eq!(ws.sheet_data.rows[0].cells.len(), 7);
1144 }
1145
1146 #[test]
1147 fn test_col_width_invalid_column_zero() {
1148 let mut sw = StreamWriter::new("Sheet1");
1149 let result = sw.set_col_width(0, 10.0);
1150 assert!(result.is_err());
1151 assert!(matches!(result.unwrap_err(), Error::InvalidColumnNumber(0)));
1152 }
1153
1154 #[test]
1155 fn test_write_row_with_options_height() {
1156 let mut sw = StreamWriter::new("Sheet1");
1157 let opts = StreamRowOptions {
1158 height: Some(30.0),
1159 ..Default::default()
1160 };
1161 sw.write_row_with_options(1, &[CellValue::from("tall row")], &opts)
1162 .unwrap();
1163 let ws = finish_and_parse(sw);
1164
1165 assert_eq!(ws.sheet_data.rows[0].ht, Some(30.0));
1166 assert_eq!(ws.sheet_data.rows[0].custom_height, Some(true));
1167 }
1168
1169 #[test]
1170 fn test_write_row_with_options_hidden() {
1171 let mut sw = StreamWriter::new("Sheet1");
1172 let opts = StreamRowOptions {
1173 visible: Some(false),
1174 ..Default::default()
1175 };
1176 sw.write_row_with_options(1, &[CellValue::from("hidden")], &opts)
1177 .unwrap();
1178 let ws = finish_and_parse(sw);
1179
1180 assert_eq!(ws.sheet_data.rows[0].hidden, Some(true));
1181 }
1182
1183 #[test]
1184 fn test_write_row_with_options_outline_level() {
1185 let mut sw = StreamWriter::new("Sheet1");
1186 let opts = StreamRowOptions {
1187 outline_level: Some(3),
1188 ..Default::default()
1189 };
1190 sw.write_row_with_options(1, &[CellValue::from("grouped")], &opts)
1191 .unwrap();
1192 let ws = finish_and_parse(sw);
1193
1194 assert_eq!(ws.sheet_data.rows[0].outline_level, Some(3));
1195 }
1196
1197 #[test]
1198 fn test_write_row_with_options_style() {
1199 let mut sw = StreamWriter::new("Sheet1");
1200 let opts = StreamRowOptions {
1201 style_id: Some(2),
1202 ..Default::default()
1203 };
1204 sw.write_row_with_options(1, &[CellValue::from("styled row")], &opts)
1205 .unwrap();
1206 let ws = finish_and_parse(sw);
1207
1208 assert_eq!(ws.sheet_data.rows[0].s, Some(2));
1209 assert_eq!(ws.sheet_data.rows[0].custom_format, Some(true));
1210 }
1211
1212 #[test]
1213 fn test_write_row_with_options_all() {
1214 let mut sw = StreamWriter::new("Sheet1");
1215 let opts = StreamRowOptions {
1216 height: Some(25.5),
1217 visible: Some(false),
1218 outline_level: Some(2),
1219 style_id: Some(7),
1220 };
1221 sw.write_row_with_options(1, &[CellValue::from("all options")], &opts)
1222 .unwrap();
1223 let ws = finish_and_parse(sw);
1224
1225 let row = &ws.sheet_data.rows[0];
1226 assert_eq!(row.ht, Some(25.5));
1227 assert_eq!(row.custom_height, Some(true));
1228 assert_eq!(row.hidden, Some(true));
1229 assert_eq!(row.outline_level, Some(2));
1230 assert_eq!(row.s, Some(7));
1231 assert_eq!(row.custom_format, Some(true));
1232 }
1233
1234 #[test]
1235 fn test_write_row_with_options_height_exceeded() {
1236 let mut sw = StreamWriter::new("Sheet1");
1237 let opts = StreamRowOptions {
1238 height: Some(500.0),
1239 ..Default::default()
1240 };
1241 let result = sw.write_row_with_options(1, &[CellValue::from("too tall")], &opts);
1242 assert!(result.is_err());
1243 assert!(matches!(
1244 result.unwrap_err(),
1245 Error::RowHeightExceeded { .. }
1246 ));
1247 }
1248
1249 #[test]
1250 fn test_write_row_with_options_outline_level_exceeded() {
1251 let mut sw = StreamWriter::new("Sheet1");
1252 let opts = StreamRowOptions {
1253 outline_level: Some(8),
1254 ..Default::default()
1255 };
1256 let result = sw.write_row_with_options(1, &[CellValue::from("bad level")], &opts);
1257 assert!(result.is_err());
1258 }
1259
1260 #[test]
1261 fn test_write_row_with_options_visible_true_no_hidden_attr() {
1262 let mut sw = StreamWriter::new("Sheet1");
1263 let opts = StreamRowOptions {
1264 visible: Some(true),
1265 ..Default::default()
1266 };
1267 sw.write_row_with_options(1, &[CellValue::from("visible")], &opts)
1268 .unwrap();
1269 let xml = finish_and_get_xml(sw);
1270
1271 assert!(!xml.contains("hidden="));
1273 }
1274
1275 #[test]
1276 fn test_col_style() {
1277 let mut sw = StreamWriter::new("Sheet1");
1278 sw.set_col_style(1, 3).unwrap();
1279 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1280 let ws = finish_and_parse(sw);
1281
1282 let cols = ws.cols.unwrap();
1283 let styled_col = cols.cols.iter().find(|c| c.style.is_some()).unwrap();
1284 assert_eq!(styled_col.style, Some(3));
1285 assert_eq!(styled_col.min, 1);
1286 assert_eq!(styled_col.max, 1);
1287 }
1288
1289 #[test]
1290 fn test_col_visible() {
1291 let mut sw = StreamWriter::new("Sheet1");
1292 sw.set_col_visible(2, false).unwrap();
1293 sw.write_row(1, &[CellValue::from("a"), CellValue::from("b")])
1294 .unwrap();
1295 let ws = finish_and_parse(sw);
1296
1297 let cols = ws.cols.unwrap();
1298 let hidden_col = cols.cols.iter().find(|c| c.hidden == Some(true)).unwrap();
1299 assert_eq!(hidden_col.min, 2);
1300 assert_eq!(hidden_col.max, 2);
1301 }
1302
1303 #[test]
1304 fn test_col_outline_level() {
1305 let mut sw = StreamWriter::new("Sheet1");
1306 sw.set_col_outline_level(3, 2).unwrap();
1307 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1308 let ws = finish_and_parse(sw);
1309
1310 let cols = ws.cols.unwrap();
1311 let outlined_col = cols
1312 .cols
1313 .iter()
1314 .find(|c| c.outline_level.is_some())
1315 .unwrap();
1316 assert_eq!(outlined_col.outline_level, Some(2));
1317 assert_eq!(outlined_col.min, 3);
1318 assert_eq!(outlined_col.max, 3);
1319 }
1320
1321 #[test]
1322 fn test_col_outline_level_exceeded() {
1323 let mut sw = StreamWriter::new("Sheet1");
1324 let result = sw.set_col_outline_level(1, 8);
1325 assert!(result.is_err());
1326 }
1327
1328 #[test]
1329 fn test_col_style_after_rows_error() {
1330 let mut sw = StreamWriter::new("Sheet1");
1331 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1332 let result = sw.set_col_style(1, 1);
1333 assert!(result.is_err());
1334 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1335 }
1336
1337 #[test]
1338 fn test_col_visible_after_rows_error() {
1339 let mut sw = StreamWriter::new("Sheet1");
1340 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1341 let result = sw.set_col_visible(1, false);
1342 assert!(result.is_err());
1343 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1344 }
1345
1346 #[test]
1347 fn test_col_outline_after_rows_error() {
1348 let mut sw = StreamWriter::new("Sheet1");
1349 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1350 let result = sw.set_col_outline_level(1, 1);
1351 assert!(result.is_err());
1352 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1353 }
1354
1355 #[test]
1356 fn test_freeze_panes_rows() {
1357 let mut sw = StreamWriter::new("Sheet1");
1358 sw.set_freeze_panes("A2").unwrap();
1359 sw.write_row(1, &[CellValue::from("header")]).unwrap();
1360 sw.write_row(2, &[CellValue::from("data")]).unwrap();
1361 let ws = finish_and_parse(sw);
1362
1363 let views = ws.sheet_views.unwrap();
1364 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1365 assert_eq!(pane.y_split, Some(1));
1366 assert_eq!(pane.top_left_cell, Some("A2".to_string()));
1367 assert_eq!(pane.active_pane, Some("bottomLeft".to_string()));
1368 assert_eq!(pane.state, Some("frozen".to_string()));
1369 assert_eq!(pane.x_split, None);
1371 }
1372
1373 #[test]
1374 fn test_freeze_panes_cols() {
1375 let mut sw = StreamWriter::new("Sheet1");
1376 sw.set_freeze_panes("B1").unwrap();
1377 sw.write_row(1, &[CellValue::from("a"), CellValue::from("b")])
1378 .unwrap();
1379 let ws = finish_and_parse(sw);
1380
1381 let views = ws.sheet_views.unwrap();
1382 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1383 assert_eq!(pane.x_split, Some(1));
1384 assert_eq!(pane.top_left_cell, Some("B1".to_string()));
1385 assert_eq!(pane.active_pane, Some("topRight".to_string()));
1386 assert_eq!(pane.state, Some("frozen".to_string()));
1387 assert_eq!(pane.y_split, None);
1389 }
1390
1391 #[test]
1392 fn test_freeze_panes_both() {
1393 let mut sw = StreamWriter::new("Sheet1");
1394 sw.set_freeze_panes("C3").unwrap();
1395 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1396 let ws = finish_and_parse(sw);
1397
1398 let views = ws.sheet_views.unwrap();
1399 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1400 assert_eq!(pane.x_split, Some(2));
1401 assert_eq!(pane.y_split, Some(2));
1402 assert_eq!(pane.top_left_cell, Some("C3".to_string()));
1403 assert_eq!(pane.active_pane, Some("bottomRight".to_string()));
1404 assert_eq!(pane.state, Some("frozen".to_string()));
1405 }
1406
1407 #[test]
1408 fn test_freeze_panes_after_rows_error() {
1409 let mut sw = StreamWriter::new("Sheet1");
1410 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1411 let result = sw.set_freeze_panes("A2");
1412 assert!(result.is_err());
1413 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1414 }
1415
1416 #[test]
1417 fn test_freeze_panes_a1_error() {
1418 let mut sw = StreamWriter::new("Sheet1");
1419 let result = sw.set_freeze_panes("A1");
1420 assert!(result.is_err());
1421 assert!(matches!(
1422 result.unwrap_err(),
1423 Error::InvalidCellReference(_)
1424 ));
1425 }
1426
1427 #[test]
1428 fn test_freeze_panes_invalid_cell_error() {
1429 let mut sw = StreamWriter::new("Sheet1");
1430 let result = sw.set_freeze_panes("ZZZZ1");
1431 assert!(result.is_err());
1432 }
1433
1434 #[test]
1435 fn test_freeze_panes_appears_before_cols() {
1436 let mut sw = StreamWriter::new("Sheet1");
1437 sw.set_freeze_panes("A2").unwrap();
1438 sw.set_col_width(1, 20.0).unwrap();
1439 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1440 let xml = finish_and_get_xml(sw);
1441
1442 let views_pos = xml.find("<sheetView").unwrap();
1443 let cols_pos = xml.find("<cols>").unwrap();
1444 let data_pos = xml.find("<sheetData").unwrap();
1445 assert!(views_pos < cols_pos);
1446 assert!(cols_pos < data_pos);
1447 }
1448
1449 #[test]
1450 fn test_freeze_panes_after_finish_error() {
1451 let mut sw = StreamWriter::new("Sheet1");
1452 sw.finished = true;
1453 let result = sw.set_freeze_panes("A2");
1454 assert!(result.is_err());
1455 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1456 }
1457
1458 #[test]
1459 fn test_no_freeze_panes_no_sheet_views() {
1460 let mut sw = StreamWriter::new("Sheet1");
1461 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1462 let xml = finish_and_get_xml(sw);
1463
1464 assert!(!xml.contains("<sheetView"));
1466 }
1467
1468 #[test]
1469 fn test_write_row_backward_compat() {
1470 let mut sw = StreamWriter::new("Sheet1");
1472 sw.write_row(1, &[CellValue::from("hello")]).unwrap();
1473 let ws = finish_and_parse(sw);
1474
1475 let row = &ws.sheet_data.rows[0];
1476 assert_eq!(row.r, 1);
1477 assert!(row.ht.is_none());
1479 assert!(row.hidden.is_none());
1480 assert!(row.outline_level.is_none());
1481 assert!(row.custom_format.is_none());
1482 }
1483
1484 #[test]
1485 fn test_col_options_combined_with_widths() {
1486 let mut sw = StreamWriter::new("Sheet1");
1487 sw.set_col_width(1, 20.0).unwrap();
1488 sw.set_col_style(2, 5).unwrap();
1489 sw.set_col_visible(3, false).unwrap();
1490 sw.write_row(
1491 1,
1492 &[
1493 CellValue::from("a"),
1494 CellValue::from("b"),
1495 CellValue::from("c"),
1496 ],
1497 )
1498 .unwrap();
1499 let ws = finish_and_parse(sw);
1500
1501 let cols = ws.cols.unwrap();
1502 assert_eq!(cols.cols.len(), 3);
1503 }
1504
1505 #[test]
1506 fn test_large_scale_10000_rows() {
1507 let mut sw = StreamWriter::new("BigSheet");
1508 for i in 1..=10_000u32 {
1509 sw.write_row(
1510 i,
1511 &[
1512 CellValue::from(format!("Row {}", i)),
1513 CellValue::from(i as i32),
1514 CellValue::from(i as f64 * 1.5),
1515 ],
1516 )
1517 .unwrap();
1518 }
1519 let ws = finish_and_parse(sw);
1520
1521 assert_eq!(ws.sheet_data.rows.len(), 10_000);
1522 assert_eq!(ws.sheet_data.rows[0].r, 1);
1523 assert_eq!(ws.sheet_data.rows[9999].r, 10_000);
1524 }
1525
1526 #[test]
1527 fn test_xml_escape_in_formula() {
1528 let mut sw = StreamWriter::new("Sheet1");
1529 sw.write_row(
1530 1,
1531 &[CellValue::Formula {
1532 expr: "IF(A1>0,\"yes\",\"no\")".to_string(),
1533 result: None,
1534 }],
1535 )
1536 .unwrap();
1537 let xml = finish_and_get_xml(sw);
1538
1539 assert!(xml.contains(">"));
1541 }
1542
1543 #[test]
1544 fn test_xml_escape_in_string() {
1545 let mut sw = StreamWriter::new("Sheet1");
1546 sw.write_row(1, &[CellValue::from("Tom & Jerry <friends>")])
1547 .unwrap();
1548 let xml = finish_and_get_xml(sw);
1549
1550 assert!(xml.contains("Tom & Jerry <friends>"));
1551 }
1552
1553 #[test]
1554 fn test_into_streamed_data_returns_valid_data() {
1555 let mut sw = StreamWriter::new("Sheet1");
1556 sw.set_col_width(1, 20.0).unwrap();
1557 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1558 .unwrap();
1559 sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1560 .unwrap();
1561 sw.add_merge_cell("A1:B1").unwrap();
1562
1563 let (name, streamed) = sw.into_streamed_data().unwrap();
1564 assert_eq!(name, "Sheet1");
1565 assert!(streamed.data_len > 0);
1566 assert!(streamed.cols_xml.is_some());
1567 assert!(streamed.merge_cells_xml.is_some());
1568 }
1569
1570 #[test]
1571 fn test_streamed_sheet_data_try_clone() {
1572 let mut sw = StreamWriter::new("Sheet1");
1573 sw.set_col_width(1, 20.0).unwrap();
1574 sw.set_freeze_panes("A2").unwrap();
1575 sw.write_row(1, &[CellValue::from("Hello")]).unwrap();
1576 sw.write_row(2, &[CellValue::from("World")]).unwrap();
1577 sw.add_merge_cell("A1:B1").unwrap();
1578
1579 let (_, original) = sw.into_streamed_data().unwrap();
1580 let cloned = original.try_clone().unwrap();
1581
1582 assert_eq!(cloned.data_len, original.data_len);
1584 assert_eq!(cloned.cols_xml, original.cols_xml);
1585 assert_eq!(cloned.sheet_views_xml, original.sheet_views_xml);
1586 assert_eq!(cloned.merge_cells_xml, original.merge_cells_xml);
1587
1588 assert_ne!(original.temp_file.path(), cloned.temp_file.path());
1590 let orig_bytes = std::fs::read(original.temp_file.path()).unwrap();
1591 let clone_bytes = std::fs::read(cloned.temp_file.path()).unwrap();
1592 assert_eq!(orig_bytes, clone_bytes);
1593 assert!(!orig_bytes.is_empty());
1594 }
1595
1596 #[test]
1597 fn test_date_value() {
1598 let mut sw = StreamWriter::new("Sheet1");
1599 sw.write_row(1, &[CellValue::Date(45306.0)]).unwrap();
1601 let xml = finish_and_get_xml(sw);
1602
1603 assert!(xml.contains("<v>45306</v>"));
1605 assert!(!xml.contains("t=\"inlineStr\""));
1607 assert!(!xml.contains("t=\"b\""));
1608 }
1609
1610 #[test]
1611 fn test_date_value_with_time() {
1612 let mut sw = StreamWriter::new("Sheet1");
1613 sw.write_row(1, &[CellValue::Date(45306.5)]).unwrap();
1615 let xml = finish_and_get_xml(sw);
1616
1617 assert!(xml.contains("<v>45306.5</v>"));
1618 }
1619
1620 #[test]
1621 fn test_rich_string_to_inline_plain_text() {
1622 use crate::rich_text::RichTextRun;
1623 let mut sw = StreamWriter::new("Sheet1");
1624 sw.write_row(
1625 1,
1626 &[CellValue::RichString(vec![
1627 RichTextRun {
1628 text: "Hello ".to_string(),
1629 font: None,
1630 size: None,
1631 bold: true,
1632 italic: false,
1633 color: None,
1634 },
1635 RichTextRun {
1636 text: "World".to_string(),
1637 font: None,
1638 size: None,
1639 bold: false,
1640 italic: true,
1641 color: None,
1642 },
1643 ])],
1644 )
1645 .unwrap();
1646 let xml = finish_and_get_xml(sw);
1647
1648 assert!(xml.contains("t=\"inlineStr\""));
1650 assert!(xml.contains("<is><t>Hello World</t></is>"));
1651 }
1652
1653 #[test]
1654 fn test_rich_string_xml_escaping() {
1655 use crate::rich_text::RichTextRun;
1656 let mut sw = StreamWriter::new("Sheet1");
1657 sw.write_row(
1658 1,
1659 &[CellValue::RichString(vec![RichTextRun {
1660 text: "A & B <C>".to_string(),
1661 font: None,
1662 size: None,
1663 bold: false,
1664 italic: false,
1665 color: None,
1666 }])],
1667 )
1668 .unwrap();
1669 let xml = finish_and_get_xml(sw);
1670
1671 assert!(xml.contains("A & B <C>"));
1672 }
1673
1674 #[test]
1675 fn test_formula_with_numeric_result() {
1676 let mut sw = StreamWriter::new("Sheet1");
1677 sw.write_row(
1678 1,
1679 &[CellValue::Formula {
1680 expr: "SUM(A2:A10)".to_string(),
1681 result: Some(Box::new(CellValue::Number(55.0))),
1682 }],
1683 )
1684 .unwrap();
1685 let xml = finish_and_get_xml(sw);
1686
1687 assert!(xml.contains("<f>SUM(A2:A10)</f>"));
1688 assert!(xml.contains("<v>55</v>"));
1689 assert!(!xml.contains("t=\"str\""));
1691 assert!(!xml.contains("t=\"b\""));
1692 assert!(!xml.contains("t=\"e\""));
1693 }
1694
1695 #[test]
1696 fn test_formula_with_error_result() {
1697 let mut sw = StreamWriter::new("Sheet1");
1698 sw.write_row(
1699 1,
1700 &[CellValue::Formula {
1701 expr: "1/0".to_string(),
1702 result: Some(Box::new(CellValue::Error("#DIV/0!".to_string()))),
1703 }],
1704 )
1705 .unwrap();
1706 let xml = finish_and_get_xml(sw);
1707
1708 assert!(xml.contains("t=\"e\""));
1709 assert!(xml.contains("<f>1/0</f>"));
1710 assert!(xml.contains("<v>#DIV/0!</v>"));
1711 }
1712
1713 #[test]
1714 fn test_formula_with_string_result() {
1715 let mut sw = StreamWriter::new("Sheet1");
1716 sw.write_row(
1717 1,
1718 &[CellValue::Formula {
1719 expr: "IF(A1>0,\"yes\",\"no\")".to_string(),
1720 result: Some(Box::new(CellValue::String("yes".to_string()))),
1721 }],
1722 )
1723 .unwrap();
1724 let xml = finish_and_get_xml(sw);
1725
1726 assert!(xml.contains("t=\"str\""));
1727 assert!(xml.contains("<f>"));
1728 assert!(xml.contains("<v>yes</v>"));
1729 }
1730
1731 #[test]
1732 fn test_formula_with_bool_result() {
1733 let mut sw = StreamWriter::new("Sheet1");
1734 sw.write_row(
1735 1,
1736 &[CellValue::Formula {
1737 expr: "A1>0".to_string(),
1738 result: Some(Box::new(CellValue::Bool(true))),
1739 }],
1740 )
1741 .unwrap();
1742 let xml = finish_and_get_xml(sw);
1743
1744 assert!(xml.contains("t=\"b\""));
1745 assert!(xml.contains("<f>A1>0</f>"));
1746 assert!(xml.contains("<v>1</v>"));
1747 }
1748
1749 #[test]
1750 fn test_formula_without_result() {
1751 let mut sw = StreamWriter::new("Sheet1");
1752 sw.write_row(
1753 1,
1754 &[CellValue::Formula {
1755 expr: "A1+B1".to_string(),
1756 result: None,
1757 }],
1758 )
1759 .unwrap();
1760 let xml = finish_and_get_xml(sw);
1761
1762 assert!(xml.contains("<f>A1+B1</f></c>"));
1763 assert!(!xml.contains("<v>"));
1765 }
1766
1767 #[test]
1768 fn test_error_value_xml_escaping() {
1769 let mut sw = StreamWriter::new("Sheet1");
1770 sw.write_row(1, &[CellValue::Error("#VALUE!".to_string())])
1771 .unwrap();
1772 let xml = finish_and_get_xml(sw);
1773
1774 assert!(xml.contains("t=\"e\""));
1775 assert!(xml.contains("#VALUE!"));
1776 }
1777
1778 #[test]
1779 fn test_empty_row_produces_valid_xml() {
1780 let mut sw = StreamWriter::new("Sheet1");
1781 sw.write_row(1, &[CellValue::Empty, CellValue::Empty])
1783 .unwrap();
1784 let ws = finish_and_parse(sw);
1785
1786 assert_eq!(ws.sheet_data.rows.len(), 1);
1787 assert_eq!(ws.sheet_data.rows[0].r, 1);
1788 assert_eq!(ws.sheet_data.rows[0].cells.len(), 0);
1790 }
1791
1792 #[test]
1793 fn test_memory_efficiency() {
1794 let mut sw = StreamWriter::new("Sheet1");
1798 for i in 1..=100_000u32 {
1799 sw.write_row(
1800 i,
1801 &[
1802 CellValue::from(format!("Row number {}", i)),
1803 CellValue::from(i as f64),
1804 ],
1805 )
1806 .unwrap();
1807 }
1808 assert!(sw.bytes_written > 1_000_000);
1810
1811 let (_, streamed) = sw.into_streamed_data().unwrap();
1812 assert!(streamed.data_len > 1_000_000);
1813 }
1814
1815 #[test]
1816 fn test_write_rows_basic() {
1817 let mut sw = StreamWriter::new("Sheet1");
1818 sw.write_rows(
1819 1,
1820 &[
1821 vec![CellValue::from("A"), CellValue::from(1)],
1822 vec![CellValue::from("B"), CellValue::from(2)],
1823 vec![CellValue::from("C"), CellValue::from(3)],
1824 ],
1825 )
1826 .unwrap();
1827 let ws = finish_and_parse(sw);
1828
1829 assert_eq!(ws.sheet_data.rows.len(), 3);
1830 assert_eq!(ws.sheet_data.rows[0].r, 1);
1831 assert_eq!(ws.sheet_data.rows[1].r, 2);
1832 assert_eq!(ws.sheet_data.rows[2].r, 3);
1833 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
1834 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
1835 assert_eq!(ws.sheet_data.rows[2].cells[0].r, "A3");
1836 }
1837
1838 #[test]
1839 fn test_write_rows_empty() {
1840 let mut sw = StreamWriter::new("Sheet1");
1841 sw.write_rows(1, &[]).unwrap();
1842 let ws = finish_and_parse(sw);
1843
1844 assert!(ws.sheet_data.rows.is_empty());
1845 }
1846
1847 #[test]
1848 fn test_write_rows_mixed_with_single() {
1849 let mut sw = StreamWriter::new("Sheet1");
1850 sw.write_row(1, &[CellValue::from("first")]).unwrap();
1851 sw.write_rows(
1852 2,
1853 &[
1854 vec![CellValue::from("second")],
1855 vec![CellValue::from("third")],
1856 vec![CellValue::from("fourth")],
1857 ],
1858 )
1859 .unwrap();
1860 let ws = finish_and_parse(sw);
1861
1862 assert_eq!(ws.sheet_data.rows.len(), 4);
1863 assert_eq!(ws.sheet_data.rows[0].r, 1);
1864 assert_eq!(ws.sheet_data.rows[1].r, 2);
1865 assert_eq!(ws.sheet_data.rows[2].r, 3);
1866 assert_eq!(ws.sheet_data.rows[3].r, 4);
1867 }
1868
1869 #[test]
1870 fn test_write_rows_overflow() {
1871 let mut sw = StreamWriter::new("Sheet1");
1872 sw.write_row(1, &[CellValue::from("ok")]).unwrap();
1873 let result = sw.write_rows(
1874 u32::MAX - 1,
1875 &[
1876 vec![CellValue::from("a")],
1877 vec![CellValue::from("b")],
1878 vec![CellValue::from("c")],
1879 ],
1880 );
1881 assert!(result.is_err());
1882 }
1883
1884 #[test]
1885 fn test_write_rows_backward_error() {
1886 let mut sw = StreamWriter::new("Sheet1");
1887 sw.write_row(5, &[CellValue::from("row5")]).unwrap();
1888 let result = sw.write_rows(3, &[vec![CellValue::from("a")], vec![CellValue::from("b")]]);
1889 assert!(result.is_err());
1890 assert!(matches!(
1891 result.unwrap_err(),
1892 Error::StreamRowAlreadyWritten { row: 3 }
1893 ));
1894 }
1895}