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