1use std::collections::HashMap;
6use std::fs;
7use std::fs::File as FsFile;
8use std::io::{self, BufReader, Cursor, Read, Write};
9use std::path::PathBuf;
10use std::thread;
11use std::time::{SystemTime, UNIX_EPOCH};
12
13use chrono::{NaiveDate, NaiveDateTime};
14use quick_xml::Reader;
15use quick_xml::escape::escape;
16use quick_xml::events::Event;
17use quick_xml::se::to_string as xml_to_string;
18use std::time::Duration;
19
20use crate::cell::CellValue;
21use crate::constants::{
22 DEFAULT_COL_WIDTH, MAX_COLUMN_WIDTH, MAX_COLUMNS, MAX_ROW_HEIGHT, MIN_COLUMNS,
23 NAMESPACE_SPREADSHEET, STREAM_CHUNK_SIZE, TOTAL_ROWS, XML_HEADER,
24};
25use crate::date;
26use crate::errors::{
27 ErrColumnNumber, ErrColumnWidth, ErrMaxRowHeight, ErrOutlineLevel, ErrSheetNotExist, Result,
28};
29use crate::file::File;
30use crate::lib_util::{
31 cell_name_to_coordinates, cell_refs_to_coordinates, coordinates_to_cell_name,
32 coordinates_to_range_ref, range_ref_to_coordinates, sort_coordinates,
33};
34use crate::styles::Style;
35use crate::xml::common::{RichTextRun, XlsxT};
36use crate::xml::shared_strings::XlsxSi;
37use crate::xml::table::{
38 Table as TableOptions, XlsxAutoFilter, XlsxTable, XlsxTableColumn, XlsxTableColumns,
39 XlsxTableStyleInfo,
40};
41use crate::xml::worksheet::{
42 Panes, XlsxBreaks, XlsxBrk, XlsxC, XlsxCol, XlsxColBreaks, XlsxCols, XlsxF, XlsxPane,
43 XlsxRowBreaks, XlsxSelection, XlsxSheetViews, XlsxWorksheet,
44};
45
46#[derive(Debug, Clone, PartialEq)]
51pub struct Cell {
52 pub style_id: i32,
53 pub formula: String,
54 pub value: Option<CellValue>,
55}
56
57impl Default for Cell {
58 fn default() -> Self {
59 Self {
60 style_id: 0,
61 formula: String::new(),
62 value: None,
63 }
64 }
65}
66
67#[derive(Debug, Default, Clone, Copy, PartialEq)]
69pub struct RowOpts {
70 pub height: f64,
71 pub hidden: bool,
72 pub style_id: i32,
73 pub outline_level: i32,
74}
75
76pub struct StreamCell {
78 style_id: i32,
79 formula: String,
80 value: Option<CellValue>,
81}
82
83pub trait StreamCellValue {
85 fn to_stream(&self) -> StreamCell;
86}
87
88impl StreamCellValue for CellValue {
89 fn to_stream(&self) -> StreamCell {
90 StreamCell {
91 style_id: 0,
92 formula: String::new(),
93 value: Some(self.clone()),
94 }
95 }
96}
97
98impl StreamCellValue for Cell {
99 fn to_stream(&self) -> StreamCell {
100 StreamCell {
101 style_id: self.style_id,
102 formula: self.formula.clone(),
103 value: self.value.clone(),
104 }
105 }
106}
107
108impl StreamCellValue for &Cell {
109 fn to_stream(&self) -> StreamCell {
110 (*self).to_stream()
111 }
112}
113
114impl<T: StreamCellValue> StreamCellValue for Option<T> {
115 fn to_stream(&self) -> StreamCell {
116 match self {
117 Some(v) => v.to_stream(),
118 None => StreamCell {
119 style_id: 0,
120 formula: String::new(),
121 value: None,
122 },
123 }
124 }
125}
126
127macro_rules! impl_stream_cell_value_for_int {
128 ($t:ty) => {
129 impl StreamCellValue for $t {
130 fn to_stream(&self) -> StreamCell {
131 CellValue::Int(*self as i64).to_stream()
132 }
133 }
134 };
135}
136
137impl_stream_cell_value_for_int!(i8);
138impl_stream_cell_value_for_int!(i16);
139impl_stream_cell_value_for_int!(i32);
140impl_stream_cell_value_for_int!(i64);
141impl_stream_cell_value_for_int!(isize);
142impl_stream_cell_value_for_int!(u8);
143impl_stream_cell_value_for_int!(u16);
144impl_stream_cell_value_for_int!(u32);
145impl_stream_cell_value_for_int!(u64);
146impl_stream_cell_value_for_int!(usize);
147
148impl StreamCellValue for f32 {
149 fn to_stream(&self) -> StreamCell {
150 CellValue::Float(*self as f64).to_stream()
151 }
152}
153
154impl StreamCellValue for f64 {
155 fn to_stream(&self) -> StreamCell {
156 CellValue::Float(*self).to_stream()
157 }
158}
159
160impl StreamCellValue for bool {
161 fn to_stream(&self) -> StreamCell {
162 CellValue::Bool(*self).to_stream()
163 }
164}
165
166impl StreamCellValue for &str {
167 fn to_stream(&self) -> StreamCell {
168 CellValue::String(self.to_string()).to_stream()
169 }
170}
171
172impl StreamCellValue for String {
173 fn to_stream(&self) -> StreamCell {
174 CellValue::String(self.clone()).to_stream()
175 }
176}
177
178impl StreamCellValue for NaiveDateTime {
179 fn to_stream(&self) -> StreamCell {
180 CellValue::DateTime(*self).to_stream()
181 }
182}
183
184impl StreamCellValue for NaiveDate {
185 fn to_stream(&self) -> StreamCell {
186 CellValue::Date(*self).to_stream()
187 }
188}
189
190impl StreamCellValue for chrono::NaiveTime {
191 fn to_stream(&self) -> StreamCell {
192 CellValue::Time(*self).to_stream()
193 }
194}
195
196impl StreamCellValue for Duration {
197 fn to_stream(&self) -> StreamCell {
198 CellValue::Float(self.as_nanos() as f64 / 86_400_000_000_000.0).to_stream()
199 }
200}
201
202impl StreamCellValue for Vec<RichTextRun> {
203 fn to_stream(&self) -> StreamCell {
204 CellValue::RichText(self.clone()).to_stream()
205 }
206}
207
208impl StreamCellValue for num_complex::Complex32 {
209 fn to_stream(&self) -> StreamCell {
210 CellValue::String(self.to_string()).to_stream()
211 }
212}
213
214impl StreamCellValue for num_complex::Complex64 {
215 fn to_stream(&self) -> StreamCell {
216 CellValue::String(self.to_string()).to_stream()
217 }
218}
219
220#[derive(Debug)]
222pub struct StreamWriter<'a> {
223 file: &'a File,
224 sheet: String,
225 #[allow(dead_code)]
226 sheet_id: i32,
227 sheet_written: bool,
228 worksheet: XlsxWorksheet,
229 raw_data: BufferedWriter,
230 rows: i32,
231 merge_cells_count: i32,
232 merge_cells: String,
233 table_parts: String,
234 flushed: bool,
235}
236
237impl<'a> StreamWriter<'a> {
238 pub fn new(file: &'a File, sheet: &str) -> Result<Self> {
240 crate::excelize::check_sheet_name(sheet)?;
241 let sheet_id = file.get_sheet_id(sheet);
242 if sheet_id == -1 {
243 return Err(Box::new(ErrSheetNotExist {
244 sheet_name: sheet.to_string(),
245 }));
246 }
247
248 let worksheet = file.work_sheet_reader(sheet)?;
249 let tmp_dir = {
250 let opts = file.options.lock().unwrap();
251 opts.tmp_dir.clone()
252 };
253
254 let mut raw_data = BufferedWriter::new(tmp_dir);
255 raw_data.write_str(&format!(
256 "{XML_HEADER}<worksheet{TEMPLATE_NAMESPACE_ID_MAP}"
257 ))?;
258 bulk_append_fields(&mut raw_data, &worksheet, 3, 4)?;
259
260 Ok(Self {
261 file,
262 sheet: sheet.to_string(),
263 sheet_id,
264 sheet_written: false,
265 worksheet,
266 raw_data,
267 rows: 0,
268 merge_cells_count: 0,
269 merge_cells: String::new(),
270 table_parts: String::new(),
271 flushed: false,
272 })
273 }
274
275 pub fn add_table(&mut self, table: &TableOptions) -> Result<()> {
277 let options = parse_table_options(table)?;
278 let mut coordinates = range_ref_to_coordinates(&options.range).map_err(|e| io_err(e))?;
279 sort_coordinates(&mut coordinates).map_err(|e| io_err(e))?;
280
281 let mut coordinates = coordinates;
283 if coordinates[1] == coordinates[3] {
284 coordinates[3] += 1;
285 }
286
287 let ref_str = coordinates_to_range_ref(&coordinates, false).map_err(|e| io_err(e))?;
288 let table_headers = self.get_row_values(coordinates[1], coordinates[0], coordinates[2])?;
289 let table_columns: Vec<XlsxTableColumn> = table_headers
290 .into_iter()
291 .enumerate()
292 .map(|(i, name)| XlsxTableColumn {
293 id: (i + 1) as i64,
294 name,
295 ..Default::default()
296 })
297 .collect();
298
299 let table_id = self.file.count_tables() + 1;
300 let name = if options.name.is_empty() {
301 format!("Table{table_id}")
302 } else {
303 options.name.clone()
304 };
305
306 let tbl = XlsxTable {
307 xmlns: Some(NAMESPACE_SPREADSHEET.to_string()),
308 id: table_id as i64,
309 name: name.clone(),
310 display_name: Some(name),
311 r#ref: ref_str.clone(),
312 auto_filter: Some(XlsxAutoFilter {
313 r#ref: ref_str,
314 ..Default::default()
315 }),
316 table_columns: Some(XlsxTableColumns {
317 count: table_columns.len() as i64,
318 table_column: table_columns,
319 }),
320 table_style_info: Some(XlsxTableStyleInfo {
321 name: Some(options.style_name.clone()),
322 show_first_column: options.show_first_column,
323 show_last_column: options.show_last_column,
324 show_row_stripes: options.show_row_stripes.unwrap_or(true),
325 show_column_stripes: options.show_column_stripes,
326 }),
327 ..Default::default()
328 };
329
330 let sheet_relationships_table_xml = format!("../tables/table{table_id}.xml");
331 let table_xml = sheet_relationships_table_xml.replace("..", "xl");
332 let sheet_xml_path = self
333 .file
334 .get_sheet_xml_path(&self.sheet)
335 .unwrap_or_default();
336 let sheet_rels = format!(
337 "xl/worksheets/_rels/{}.rels",
338 sheet_xml_path.trim_start_matches("xl/worksheets/")
339 );
340 let r_id = self.file.add_rels(
341 &sheet_rels,
342 crate::constants::SOURCE_RELATIONSHIP_TABLE,
343 &sheet_relationships_table_xml,
344 "",
345 );
346
347 self.table_parts = format!(
348 r#"<tableParts count="1"><tablePart r:id="rId{r_id}"></tablePart></tableParts>"#
349 );
350
351 self.file.add_content_type_part(table_id, "table")?;
352 let mut body = xml_to_string(&tbl)?.into_bytes();
353 crate::file::strip_empty_attributes(&mut body);
354 self.file.save_file_list(&table_xml, &body);
355 Ok(())
356 }
357
358 pub fn set_row(
364 &mut self,
365 cell: &str,
366 values: &[&dyn StreamCellValue],
367 opts: Option<RowOpts>,
368 ) -> Result<()> {
369 if self.flushed {
370 return Err(crate::errors::new_stream_set_row_order_error("SetRow").into());
371 }
372 let (col, row) = cell_name_to_coordinates(cell).map_err(|e| io_err(e))?;
373 if row <= self.rows {
374 return Err(crate::errors::new_stream_set_row_error(row).into());
375 }
376 self.rows = row;
377 self.write_sheet_data()?;
378
379 let opts = opts.unwrap_or_default();
380 let attrs = opts.marshal_attrs()?;
381 self.raw_data
382 .write_str(&format!(r#"<row r="{row}"{attrs}>"#))?;
383
384 for (i, val) in values.iter().enumerate() {
385 let val = val.to_stream();
386 if val.value.is_none() && val.formula.is_empty() && val.style_id == 0 {
390 continue;
391 }
392 let ref_str =
393 coordinates_to_cell_name(col + i as i32, row, false).map_err(|e| io_err(e))?;
394 let mut c = XlsxC {
395 r: Some(ref_str),
396 s: Some(prepare_cell_style(
397 &self.worksheet,
398 (col + i as i32) as i64,
399 row as i64,
400 opts.style_id as i64,
401 )),
402 ..Default::default()
403 };
404
405 if !val.formula.is_empty() {
406 set_cell_formula(&mut c, &val.formula);
407 }
408 if val.style_id > 0 {
409 c.s = Some(val.style_id as i64);
410 }
411 if let Some(value) = val.value {
412 if let Err(e) = self.set_cell_val_func(&mut c, value) {
413 self.raw_data.write_str("</row>")?;
414 return Err(e);
415 }
416 }
417 write_cell(&mut self.raw_data, &c)?;
418 }
419 self.raw_data.write_str("</row>")?;
420 Ok(self.raw_data.sync()?)
421 }
422
423 pub fn set_col_visible(&mut self, min_val: i32, max_val: i32, visible: bool) -> Result<()> {
425 if self.sheet_written {
426 return Err(crate::errors::new_stream_set_row_order_error("SetColVisible").into());
427 }
428 if min_val < MIN_COLUMNS
429 || min_val > MAX_COLUMNS
430 || max_val < MIN_COLUMNS
431 || max_val > MAX_COLUMNS
432 {
433 return Err(Box::new(ErrColumnNumber));
434 }
435 let (min_val, max_val) = if min_val > max_val {
436 (max_val, min_val)
437 } else {
438 (min_val, max_val)
439 };
440 set_col_visible_ws(&mut self.worksheet, min_val, max_val, visible);
441 Ok(())
442 }
443
444 pub fn set_col_outline_level(&mut self, col: i32, level: u8) -> Result<()> {
446 if self.sheet_written {
447 return Err(crate::errors::new_stream_set_row_order_error("SetColOutlineLevel").into());
448 }
449 if col < MIN_COLUMNS || col > MAX_COLUMNS {
450 return Err(Box::new(ErrColumnNumber));
451 }
452 if level == 0 || level > 7 {
453 return Err(Box::new(ErrOutlineLevel));
454 }
455 set_col_outline_level_ws(&mut self.worksheet, col, level);
456 Ok(())
457 }
458
459 pub fn set_col_style(&mut self, min_val: i32, max_val: i32, style_id: i32) -> Result<()> {
461 if self.sheet_written {
462 return Err(crate::errors::new_stream_set_row_order_error("SetColStyle").into());
463 }
464 if min_val < MIN_COLUMNS
465 || min_val > MAX_COLUMNS
466 || max_val < MIN_COLUMNS
467 || max_val > MAX_COLUMNS
468 {
469 return Err(Box::new(ErrColumnNumber));
470 }
471 let (min_val, max_val) = if max_val < min_val {
472 (max_val, min_val)
473 } else {
474 (min_val, max_val)
475 };
476 let styles = self.file.styles_reader()?;
477 let style_count = styles.cell_xfs.as_ref().map(|x| x.xf.len()).unwrap_or(1) as i32;
478 if style_id < 0 || style_id >= style_count {
479 return Err(crate::errors::new_invalid_style_id_error(style_id).into());
480 }
481 set_col_style_ws(&mut self.worksheet, min_val, max_val, style_id);
482 Ok(())
483 }
484
485 pub fn set_col_width(&mut self, min_val: i32, max_val: i32, width: f64) -> Result<()> {
487 if self.sheet_written {
488 return Err(crate::errors::new_stream_set_row_order_error("SetColWidth").into());
489 }
490 if min_val < MIN_COLUMNS
491 || min_val > MAX_COLUMNS
492 || max_val < MIN_COLUMNS
493 || max_val > MAX_COLUMNS
494 {
495 return Err(Box::new(ErrColumnNumber));
496 }
497 if width > MAX_COLUMN_WIDTH as f64 {
498 return Err(Box::new(ErrColumnWidth));
499 }
500 let (min_val, max_val) = if min_val > max_val {
501 (max_val, min_val)
502 } else {
503 (min_val, max_val)
504 };
505 set_col_width_ws(&mut self.worksheet, min_val, max_val, width);
506 Ok(())
507 }
508
509 pub fn insert_page_break(&mut self, cell: &str) -> Result<()> {
511 insert_page_break_ws(&mut self.worksheet, cell)
512 }
513
514 pub fn set_panes(&mut self, panes: &Panes) -> Result<()> {
516 if self.sheet_written {
517 return Err(crate::errors::new_stream_set_row_order_error("SetPanes").into());
518 }
519 set_panes_ws(&mut self.worksheet, panes)
520 }
521
522 pub fn merge_cell(&mut self, top_left_cell: &str, bottom_right_cell: &str) -> Result<()> {
524 cell_refs_to_coordinates(top_left_cell, bottom_right_cell).map_err(|e| io_err(e))?;
525 self.merge_cells_count += 1;
526 self.merge_cells.push_str(&format!(
527 r#"<mergeCell ref="{}:{}"/>"#,
528 top_left_cell, bottom_right_cell
529 ));
530 Ok(())
531 }
532
533 pub fn flush(&mut self) -> Result<()> {
535 if self.flushed {
536 return Ok(());
537 }
538 self.write_sheet_data()?;
539 self.raw_data.write_str("</sheetData>")?;
540 bulk_append_fields(&mut self.raw_data, &self.worksheet, 9, 16)?;
541
542 if self.merge_cells_count > 0 {
543 self.raw_data.write_str(&format!(
544 r#"<mergeCells count="{}">{}</mergeCells>"#,
545 self.merge_cells_count, self.merge_cells
546 ))?;
547 }
548
549 bulk_append_fields(&mut self.raw_data, &self.worksheet, 18, 39)?;
550 self.raw_data.write_str(&self.table_parts)?;
551 bulk_append_fields(&mut self.raw_data, &self.worksheet, 41, 41)?;
552 self.raw_data.write_str("</worksheet>")?;
553 self.raw_data.flush()?;
554
555 let tmp_path = self.raw_data.into_temp_file()?;
556 let sheet_path = self
557 .file
558 .get_sheet_xml_path(&self.sheet)
559 .unwrap_or_default();
560 self.file.pkg.remove(&sheet_path);
561 self.file.sheet.remove(&sheet_path);
562 self.file.checked.remove(&sheet_path);
563 if let Some(tmp_path) = tmp_path {
564 let state = crate::file::StreamState {
565 tmp_path,
566 sheet_path: sheet_path.clone(),
567 };
568 self.file.streams.borrow_mut().insert(sheet_path, state);
569 }
570 self.flushed = true;
571 Ok(())
572 }
573
574 fn write_sheet_data(&mut self) -> Result<()> {
575 if !self.sheet_written {
576 bulk_append_fields(&mut self.raw_data, &self.worksheet, 5, 6)?;
577 if let Some(cols) = &self.worksheet.cols {
578 self.raw_data.write_str("<cols>")?;
579 for col in &cols.col {
580 self.raw_data
581 .write_str(&format!(r#"<col min="{}" max="{}""#, col.min, col.max))?;
582 if let Some(width) = col.width {
583 self.raw_data
584 .write_str(&format!(r#" width="{width}" customWidth="1""#))?;
585 }
586 if let Some(style) = col.style {
587 self.raw_data.write_str(&format!(r#" style="{style}""#))?;
588 }
589 if col.hidden.unwrap_or(false) {
590 self.raw_data.write_str(r#" hidden="1""#)?;
591 }
592 if let Some(level) = col.outline_level {
593 self.raw_data
594 .write_str(&format!(r#" outlineLevel="{level}""#))?;
595 }
596 if col.collapsed.unwrap_or(false) {
597 self.raw_data.write_str(r#" collapsed="1""#)?;
598 }
599 if col.best_fit.unwrap_or(false) {
600 self.raw_data.write_str(r#" bestFit="1""#)?;
601 }
602 self.raw_data.write_str("/>")?;
603 }
604 self.raw_data.write_str("</cols>")?;
605 }
606 self.raw_data.write_str("<sheetData>")?;
607 self.sheet_written = true;
608 }
609 Ok(())
610 }
611
612 fn set_cell_val_func(&self, c: &mut XlsxC, value: CellValue) -> Result<()> {
613 match value {
614 CellValue::Int(n) => {
615 c.t = None;
616 c.v = Some(n.to_string());
617 }
618 CellValue::Float(n) => {
619 set_cell_float(c, n, -1, 64);
620 }
621 CellValue::String(s) => {
622 set_cell_value(c, &s);
623 }
624 CellValue::Bool(b) => {
625 c.t = Some("b".to_string());
626 c.v = Some(if b { "1".to_string() } else { "0".to_string() });
627 }
628 CellValue::Formula(f) => {
629 set_cell_formula(c, &f);
630 }
631 CellValue::DateTime(dt) => {
632 set_cell_time(self, c, dt, true)?;
633 }
634 CellValue::Date(d) => {
635 set_cell_time(self, c, d.and_hms_opt(0, 0, 0).unwrap_or_default(), false)?;
636 }
637 CellValue::Time(t) => {
638 c.t = None;
639 c.v = Some(format!("{:.15}", date::time_to_excel_serial(t)));
640 if c.s.unwrap_or(0) == 0 {
641 let style_id = self.file.new_style(&Style {
642 num_fmt: 21,
643 ..Default::default()
644 })?;
645 c.s = Some(style_id as i64);
646 }
647 }
648 CellValue::Duration(d) => {
649 c.t = None;
650 c.v = Some(format!(
651 "{}",
652 format_float(d.as_nanos() as f64 / 86_400_000_000_000.0)
653 ));
654 }
655 CellValue::RichText(runs) => {
656 c.t = Some("inlineStr".to_string());
657 c.is = Some(crate::cell::runs_to_xlsx_si(&runs));
658 }
659 }
660 Ok(())
661 }
662
663 fn get_row_values(&mut self, h_row: i32, h_col: i32, v_col: i32) -> Result<Vec<String>> {
664 let mut res = vec![String::new(); (v_col - h_col + 1) as usize];
665 let sst = self.file.shared_strings_reader().ok();
666 let reader = self.raw_data.reader()?;
667 let mut reader = Reader::from_reader(BufReader::new(reader));
668 let mut buf = Vec::new();
669 let mut in_target = false;
670 let mut in_cell = false;
671 let mut in_v = false;
672 let mut in_is = false;
673 let mut cell_type = None::<String>;
674 let mut current_col = 0;
675 let mut current_val = String::new();
676
677 loop {
678 match reader.read_event_into(&mut buf) {
679 Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
680 let local_name = e.local_name();
681 let name = local_name.as_ref();
682 if !in_target && name == b"row" {
683 if let Ok(Some(attr)) = e.try_get_attribute("r") {
684 if let Ok(r_str) = attr.decode_and_unescape_value(reader.decoder()) {
685 if r_str.parse::<i32>().unwrap_or(0) == h_row {
686 in_target = true;
687 }
688 }
689 }
690 } else if in_target && name == b"c" {
691 in_cell = true;
692 current_col = 0;
693 cell_type = None;
694 current_val.clear();
695 if let Ok(Some(attr)) = e.try_get_attribute("r") {
696 if let Ok(r_str) = attr.decode_and_unescape_value(reader.decoder()) {
697 if let Ok((col, _)) = cell_name_to_coordinates(&r_str) {
698 current_col = col;
699 }
700 }
701 }
702 if let Ok(Some(attr)) = e.try_get_attribute("t") {
703 if let Ok(t) = attr.decode_and_unescape_value(reader.decoder()) {
704 cell_type = Some(t.to_string());
705 }
706 }
707 } else if in_cell && name == b"v" {
708 in_v = true;
709 current_val.clear();
710 } else if in_cell && name == b"is" {
711 in_is = true;
712 current_val.clear();
713 }
714 }
715 Ok(Event::Text(e)) => {
716 if in_v || in_is {
717 if let Ok(text) = e.unescape() {
718 current_val.push_str(&text);
719 }
720 }
721 }
722 Ok(Event::End(e)) => {
723 let local_name = e.local_name();
724 let name = local_name.as_ref();
725 if name == b"row" && in_target {
726 break;
727 }
728 if in_target && name == b"v" {
729 in_v = false;
730 let val = if cell_type.as_deref() == Some("s") {
731 if let Ok(idx) = current_val.parse::<usize>() {
732 sst.as_ref()
733 .and_then(|s| s.si.get(idx))
734 .map(|si| extract_si_text(si))
735 .unwrap_or_default()
736 } else {
737 String::new()
738 }
739 } else {
740 current_val.clone()
741 };
742 if current_col >= h_col && current_col <= v_col {
743 res[(current_col - h_col) as usize] = val;
744 }
745 } else if in_target && name == b"is" {
746 in_is = false;
747 if current_col >= h_col && current_col <= v_col {
748 res[(current_col - h_col) as usize] = current_val.clone();
749 }
750 } else if in_target && name == b"c" {
751 in_cell = false;
752 cell_type = None;
753 }
754 }
755 Ok(Event::Eof) => break,
756 Err(e) => return Err(Box::new(e)),
757 _ => {}
758 }
759 buf.clear();
760 }
761 Ok(res)
762 }
763}
764
765impl<'a> Drop for StreamWriter<'a> {
766 fn drop(&mut self) {
767 if !self.flushed {
768 let _ = self.flush();
769 }
770 }
771}
772
773impl File {
774 pub fn new_stream_writer(&self, sheet: &str) -> Result<StreamWriter<'_>> {
776 StreamWriter::new(self, sheet)
777 }
778}
779
780impl RowOpts {
781 fn marshal_attrs(&self) -> Result<String> {
782 if self.height > MAX_ROW_HEIGHT as f64 {
783 return Err(Box::new(ErrMaxRowHeight));
784 }
785 if self.outline_level > 7 {
786 return Err(Box::new(ErrOutlineLevel));
787 }
788 let mut attrs = String::new();
789 if self.style_id > 0 {
790 attrs.push_str(&format!(r#" s="{}" customFormat="1""#, self.style_id));
791 }
792 if self.height > 0.0 {
793 attrs.push_str(&format!(
794 r#" ht="{}" customHeight="1""#,
795 format_float(self.height)
796 ));
797 }
798 if self.outline_level > 0 {
799 attrs.push_str(&format!(r#" outlineLevel="{}""#, self.outline_level));
800 }
801 if self.hidden {
802 attrs.push_str(r#" hidden="1""#);
803 }
804 Ok(attrs)
805 }
806}
807
808fn set_cell_formula(c: &mut XlsxC, formula: &str) {
809 if !formula.is_empty() {
810 c.t = Some("str".to_string());
811 c.f = Some(XlsxF {
812 content: formula.to_string(),
813 ..Default::default()
814 });
815 }
816}
817
818fn set_cell_time(
819 sw: &StreamWriter<'_>,
820 c: &mut XlsxC,
821 val: NaiveDateTime,
822 has_time: bool,
823) -> Result<()> {
824 let date1904 = sw
825 .file
826 .workbook_reader()?
827 .workbook_pr
828 .as_ref()
829 .and_then(|p| p.date1904)
830 .unwrap_or(false);
831 let serial = date::datetime_to_excel_serial(val, date1904);
832 let is_num = serial > 0.0;
833 if is_num {
834 c.v = Some(format!("{:.15}", serial));
835 if c.s.unwrap_or(0) == 0 {
836 let num_fmt = if has_time && val.date() != NaiveDate::default() {
837 22
838 } else if has_time {
839 21
840 } else {
841 14
842 };
843 let style_id = sw.file.new_style(&Style {
844 num_fmt,
845 ..Default::default()
846 })?;
847 c.s = Some(style_id as i64);
848 }
849 } else {
850 c.v = Some(val.format("%Y-%m-%dT%H:%M:%S").to_string());
851 }
852 Ok(())
853}
854
855fn set_cell_float(c: &mut XlsxC, value: f64, precision: i32, _bit_size: u32) {
856 if value.is_nan() || value.is_infinite() {
857 c.t = Some("inlineStr".to_string());
858 c.v = Some(String::new());
859 c.is = Some(XlsxSi {
860 t: Some(XlsxT {
861 space: None,
862 val: value.to_string(),
863 }),
864 ..Default::default()
865 });
866 return;
867 }
868 c.t = None;
869 c.v = Some(if precision < 0 {
870 format_float(value)
871 } else {
872 format!("{value:.precision$}", precision = precision as usize)
873 });
874}
875
876fn set_cell_value(c: &mut XlsxC, val: &str) {
877 if c.f.is_some() {
878 c.t = Some("str".to_string());
879 c.v = Some(val.to_string());
880 if needs_space_preserve(val) {
881 c.xml_space = Some("preserve".to_string());
882 }
883 } else {
884 c.t = Some("inlineStr".to_string());
885 c.v = Some(String::new());
886 c.is = Some(XlsxSi {
887 t: Some(XlsxT {
888 space: if needs_space_preserve(val) {
889 Some("preserve".to_string())
890 } else {
891 None
892 },
893 val: val.to_string(),
894 }),
895 ..Default::default()
896 });
897 }
898}
899
900fn write_cell(buf: &mut BufferedWriter, c: &XlsxC) -> Result<()> {
901 buf.write_str("<c")?;
902 if let Some(space) = &c.xml_space {
903 buf.write_str(&format!(r#" xml:space="{space}""#))?;
904 }
905 if let Some(r) = &c.r {
906 buf.write_str(&format!(r#" r="{r}""#))?;
907 }
908 if let Some(s) = c.s {
909 if s != 0 {
910 buf.write_str(&format!(r#" s="{s}""#))?;
911 }
912 }
913 if let Some(t) = &c.t {
914 buf.write_str(&format!(r#" t="{t}""#))?;
915 }
916 buf.write_str(">")?;
917
918 if let Some(f) = &c.f {
919 buf.write_str("<f>")?;
920 buf.write_str(&escape_xml(&f.content))?;
921 buf.write_str("</f>")?;
922 }
923 if let Some(v) = &c.v {
924 if !v.is_empty() {
925 buf.write_str("<v>")?;
926 buf.write_str(&escape_xml(v))?;
927 buf.write_str("</v>")?;
928 }
929 }
930 if let Some(is) = &c.is {
931 if !is.r.is_empty() {
932 let runs = xml_to_string(&is.r)?;
933 buf.write_str("<is>")?;
934 buf.write_str(&runs)?;
935 buf.write_str("</is>")?;
936 } else if let Some(t) = &is.t {
937 buf.write_str("<is><t")?;
938 if let Some(space) = &t.space {
939 buf.write_str(&format!(r#" xml:space="{space}""#))?;
940 }
941 buf.write_str(">")?;
942 buf.write_str(&escape_xml(&t.val))?;
943 buf.write_str("</t></is>")?;
944 }
945 }
946 buf.write_str("</c>")?;
947 Ok(())
948}
949
950fn prepare_cell_style(ws: &XlsxWorksheet, col: i64, row: i64, style: i64) -> i64 {
951 if style != 0 {
952 return style;
953 }
954 if row > 0 && row as usize <= ws.sheet_data.row.len() {
955 if let Some(style_id) = ws.sheet_data.row[row as usize - 1].s {
956 if style_id != 0 {
957 return style_id;
958 }
959 }
960 }
961 if let Some(cols) = &ws.cols {
962 for c in &cols.col {
963 if c.min <= col && col <= c.max {
964 if let Some(style_id) = c.style {
965 if style_id != 0 {
966 return style_id;
967 }
968 }
969 }
970 }
971 }
972 style
973}
974
975fn bulk_append_fields<W: Write>(
976 w: &mut W,
977 ws: &XlsxWorksheet,
978 from: usize,
979 to: usize,
980) -> Result<()> {
981 for i in from..=to {
982 match i {
983 3 => append_field_option(w, &ws.sheet_pr)?,
984 4 => append_field_option(w, &ws.dimension)?,
985 5 => append_field_option(w, &ws.sheet_views)?,
986 6 => append_field_option(w, &ws.sheet_format_pr)?,
987 9 => append_field_option(w, &ws.sheet_calc_pr)?,
988 10 => append_field_option(w, &ws.sheet_protection)?,
989 11 => append_field_option(w, &ws.protected_ranges)?,
990 12 => append_field_option(w, &ws.scenarios)?,
991 13 => append_field_option(w, &ws.auto_filter)?,
992 14 => append_field_option(w, &ws.sort_state)?,
993 15 => append_field_option(w, &ws.data_consolidate)?,
994 16 => append_field_option(w, &ws.custom_sheet_views)?,
995 18 => append_field_option(w, &ws.phonetic_pr)?,
996 19 => append_field_vec(w, &ws.conditional_formatting)?,
997 20 => append_field_option(w, &ws.data_validations)?,
998 21 => append_field_option(w, &ws.hyperlinks)?,
999 22 => append_field_option(w, &ws.print_options)?,
1000 23 => append_field_option(w, &ws.page_margins)?,
1001 24 => append_field_option(w, &ws.page_setup)?,
1002 25 => append_field_option(w, &ws.header_footer)?,
1003 26 => append_field_option(w, &ws.row_breaks)?,
1004 27 => append_field_option(w, &ws.col_breaks)?,
1005 28 => append_field_option(w, &ws.custom_properties)?,
1006 29 => append_field_option(w, &ws.cell_watches)?,
1007 30 => append_field_option(w, &ws.ignored_errors)?,
1008 31 => append_field_option(w, &ws.smart_tags)?,
1009 32 => append_field_option(w, &ws.drawing)?,
1010 33 => append_field_option(w, &ws.legacy_drawing)?,
1011 34 => append_field_option(w, &ws.legacy_drawing_hf)?,
1012 35 => append_field_option(w, &ws.drawing_hf)?,
1013 36 => append_field_option(w, &ws.picture)?,
1014 37 => append_field_option(w, &ws.ole_objects)?,
1015 38 => append_field_option(w, &ws.controls)?,
1016 39 => append_field_option(w, &ws.web_publish_items)?,
1017 41 => append_field_option(w, &ws.table_parts)?,
1018 _ => {}
1019 }
1020 }
1021 Ok(())
1022}
1023
1024fn append_field_option<W: Write, T: serde::Serialize>(w: &mut W, opt: &Option<T>) -> Result<()> {
1025 if let Some(v) = opt {
1026 w.write_all(xml_to_string(v)?.as_bytes())?;
1027 }
1028 Ok(())
1029}
1030
1031fn append_field_vec<W: Write, T: serde::Serialize>(w: &mut W, vec: &[T]) -> Result<()> {
1032 if !vec.is_empty() {
1033 for v in vec {
1034 w.write_all(xml_to_string(v)?.as_bytes())?;
1035 }
1036 }
1037 Ok(())
1038}
1039
1040fn set_col_visible_ws(ws: &mut XlsxWorksheet, min_val: i32, max_val: i32, visible: bool) {
1045 let col_data = XlsxCol {
1046 min: min_val as i64,
1047 max: max_val as i64,
1048 width: Some(DEFAULT_COL_WIDTH),
1049 hidden: Some(!visible),
1050 custom_width: Some(true),
1051 ..Default::default()
1052 };
1053 ws.cols = Some(XlsxCols {
1054 col: flat_cols(
1055 col_data,
1056 ws.cols.as_ref().map(|c| c.col.as_slice()).unwrap_or(&[]),
1057 |fc, c| {
1058 fc.best_fit = c.best_fit;
1059 fc.collapsed = c.collapsed;
1060 fc.custom_width = c.custom_width;
1061 fc.outline_level = c.outline_level;
1062 fc.phonetic = c.phonetic;
1063 fc.style = c.style;
1064 fc.width = c.width;
1065 },
1066 ),
1067 });
1068}
1069
1070fn set_col_outline_level_ws(ws: &mut XlsxWorksheet, col_num: i32, level: u8) {
1071 let col_data = XlsxCol {
1072 min: col_num as i64,
1073 max: col_num as i64,
1074 outline_level: Some(level),
1075 custom_width: Some(true),
1076 ..Default::default()
1077 };
1078 ws.cols = Some(XlsxCols {
1079 col: flat_cols(
1080 col_data,
1081 ws.cols.as_ref().map(|c| c.col.as_slice()).unwrap_or(&[]),
1082 |fc, c| {
1083 fc.best_fit = c.best_fit;
1084 fc.collapsed = c.collapsed;
1085 fc.custom_width = c.custom_width;
1086 fc.hidden = c.hidden;
1087 fc.phonetic = c.phonetic;
1088 fc.style = c.style;
1089 fc.width = c.width;
1090 },
1091 ),
1092 });
1093}
1094
1095fn set_col_style_ws(ws: &mut XlsxWorksheet, min_val: i32, max_val: i32, style_id: i32) {
1096 let width = ws
1097 .sheet_format_pr
1098 .as_ref()
1099 .and_then(|f| f.default_col_width)
1100 .unwrap_or(DEFAULT_COL_WIDTH);
1101 let col_data = XlsxCol {
1102 min: min_val as i64,
1103 max: max_val as i64,
1104 width: Some(width),
1105 style: Some(style_id as i64),
1106 ..Default::default()
1107 };
1108 ws.cols = Some(XlsxCols {
1109 col: flat_cols(
1110 col_data,
1111 ws.cols.as_ref().map(|c| c.col.as_slice()).unwrap_or(&[]),
1112 |fc, c| {
1113 fc.best_fit = c.best_fit;
1114 fc.collapsed = c.collapsed;
1115 fc.custom_width = c.custom_width;
1116 fc.hidden = c.hidden;
1117 fc.outline_level = c.outline_level;
1118 fc.phonetic = c.phonetic;
1119 fc.width = c.width;
1120 },
1121 ),
1122 });
1123}
1124
1125fn set_col_width_ws(ws: &mut XlsxWorksheet, min_val: i32, max_val: i32, width: f64) {
1126 let col_data = XlsxCol {
1127 min: min_val as i64,
1128 max: max_val as i64,
1129 width: Some(width),
1130 custom_width: Some(true),
1131 ..Default::default()
1132 };
1133 ws.cols = Some(XlsxCols {
1134 col: flat_cols(
1135 col_data,
1136 ws.cols.as_ref().map(|c| c.col.as_slice()).unwrap_or(&[]),
1137 |fc, c| {
1138 fc.best_fit = c.best_fit;
1139 fc.collapsed = c.collapsed;
1140 fc.hidden = c.hidden;
1141 fc.outline_level = c.outline_level;
1142 fc.phonetic = c.phonetic;
1143 fc.style = c.style;
1144 },
1145 ),
1146 });
1147}
1148
1149fn flat_cols<F>(col: XlsxCol, cols: &[XlsxCol], mut replacer: F) -> Vec<XlsxCol>
1150where
1151 F: FnMut(&mut XlsxCol, &XlsxCol),
1152{
1153 let mut fc: HashMap<i64, XlsxCol> = HashMap::new();
1154 for i in col.min..=col.max {
1155 let mut c = col.clone();
1156 c.min = i;
1157 c.max = i;
1158 fc.insert(i, c);
1159 }
1160 for column in cols {
1161 for i in column.min..=column.max {
1162 if let Some(existing) = fc.get_mut(&i) {
1163 replacer(existing, column);
1164 } else {
1165 let mut c = column.clone();
1166 c.min = i;
1167 c.max = i;
1168 fc.insert(i, c);
1169 }
1170 }
1171 }
1172 let mut result: Vec<XlsxCol> = fc.into_values().collect();
1173 result.sort_by_key(|c| c.min);
1174 result
1175}
1176
1177fn set_panes_ws(ws: &mut XlsxWorksheet, panes: &Panes) -> Result<()> {
1182 if panes.selection.is_empty() && !panes.freeze && !panes.split {
1183 if let Some(views) = ws.sheet_views.as_mut() {
1184 if let Some(view) = views.sheet_view.last_mut() {
1185 view.pane = None;
1186 }
1187 }
1188 return Ok(());
1189 }
1190
1191 let pane = XlsxPane {
1192 active_pane: Some(panes.active_pane.clone()).filter(|s| !s.is_empty()),
1193 top_left_cell: Some(panes.top_left_cell.clone()).filter(|s| !s.is_empty()),
1194 x_split: Some(panes.x_split as f64),
1195 y_split: Some(panes.y_split as f64),
1196 state: if panes.freeze {
1197 Some("frozen".to_string())
1198 } else {
1199 None
1200 },
1201 };
1202
1203 if ws.sheet_views.is_none() {
1204 ws.sheet_views = Some(XlsxSheetViews::default());
1205 }
1206 let views = ws.sheet_views.as_mut().unwrap();
1207 if views.sheet_view.is_empty() {
1208 views
1209 .sheet_view
1210 .push(crate::xml::worksheet::XlsxSheetView::default());
1211 }
1212 let view = views.sheet_view.last_mut().unwrap();
1213 view.pane = Some(pane);
1214
1215 let selections: Vec<XlsxSelection> = panes
1216 .selection
1217 .iter()
1218 .map(|s| XlsxSelection {
1219 active_cell: Some(s.active_cell.clone()).filter(|s| !s.is_empty()),
1220 active_cell_id: None,
1221 pane: Some(s.pane.clone()).filter(|s| !s.is_empty()),
1222 sqref: Some(s.sqref.clone()).filter(|s| !s.is_empty()),
1223 })
1224 .collect();
1225 view.selection = selections;
1226 Ok(())
1227}
1228
1229fn insert_page_break_ws(ws: &mut XlsxWorksheet, cell: &str) -> Result<()> {
1230 let (mut col, mut row) = cell_name_to_coordinates(cell).map_err(|e| io_err(e))?;
1231 col -= 1;
1232 row -= 1;
1233 if col == 0 && row == 0 {
1234 return Ok(());
1235 }
1236 if ws.row_breaks.is_none() {
1237 ws.row_breaks = Some(XlsxRowBreaks {
1238 breaks: XlsxBreaks::default(),
1239 });
1240 }
1241 if ws.col_breaks.is_none() {
1242 ws.col_breaks = Some(XlsxColBreaks {
1243 breaks: XlsxBreaks::default(),
1244 });
1245 }
1246
1247 let row_breaks = ws.row_breaks.as_mut().unwrap();
1248 let col_breaks = ws.col_breaks.as_mut().unwrap();
1249
1250 let row_exists = row_breaks
1251 .breaks
1252 .brk
1253 .iter()
1254 .any(|b| b.id == Some(row as i64));
1255 let col_exists = col_breaks
1256 .breaks
1257 .brk
1258 .iter()
1259 .any(|b| b.id == Some(col as i64));
1260
1261 if row != 0 && !row_exists {
1262 row_breaks.breaks.brk.push(XlsxBrk {
1263 id: Some(row as i64),
1264 max: Some(MAX_COLUMNS as i64 - 1),
1265 min: None,
1266 man: Some(true),
1267 pt: None,
1268 });
1269 row_breaks.breaks.manual_break_count =
1270 Some(row_breaks.breaks.manual_break_count.unwrap_or(0) + 1);
1271 }
1272 if col != 0 && !col_exists {
1273 col_breaks.breaks.brk.push(XlsxBrk {
1274 id: Some(col as i64),
1275 max: Some(TOTAL_ROWS as i64 - 1),
1276 min: None,
1277 man: Some(true),
1278 pt: None,
1279 });
1280 col_breaks.breaks.manual_break_count =
1281 Some(col_breaks.breaks.manual_break_count.unwrap_or(0) + 1);
1282 }
1283 row_breaks.breaks.count = Some(row_breaks.breaks.brk.len() as i64);
1284 col_breaks.breaks.count = Some(col_breaks.breaks.brk.len() as i64);
1285 Ok(())
1286}
1287
1288fn extract_si_text(si: &XlsxSi) -> String {
1293 if let Some(t) = &si.t {
1294 return t.val.clone();
1295 }
1296 si.r.iter()
1297 .filter_map(|r| r.t.as_ref().map(|t| t.val.clone()))
1298 .collect()
1299}
1300
1301fn parse_table_options(opts: &TableOptions) -> Result<TableOptions> {
1302 let mut options = opts.clone();
1303 if options.show_row_stripes.is_none() {
1304 options.show_row_stripes = Some(true);
1305 }
1306 if !options.name.is_empty() {
1307 check_defined_name(&options.name)?;
1308 }
1309 Ok(options)
1310}
1311
1312fn check_defined_name(name: &str) -> Result<()> {
1313 if name.len() > 255 {
1314 return Err(Box::new(crate::errors::ErrNameLength));
1315 }
1316 let mut chars = name.chars();
1317 if let Some(first) = chars.next() {
1318 if !first.is_ascii_alphabetic() && first != '_' {
1319 return Err(crate::errors::new_invalid_name_error(name).into());
1320 }
1321 for c in chars {
1322 if !c.is_ascii_alphanumeric() && c != '_' && c != '.' {
1323 return Err(crate::errors::new_invalid_name_error(name).into());
1324 }
1325 }
1326 }
1327 Ok(())
1328}
1329
1330const TEMPLATE_NAMESPACE_ID_MAP: &str = crate::templates::TEMPLATE_NAMESPACE_ID_MAP;
1335
1336#[derive(Debug)]
1337struct BufferedWriter {
1338 tmp_dir: String,
1339 tmp: Option<FsFile>,
1340 tmp_path: Option<PathBuf>,
1341 buf: Vec<u8>,
1342}
1343
1344impl BufferedWriter {
1345 fn new(tmp_dir: String) -> Self {
1346 Self {
1347 tmp_dir,
1348 tmp: None,
1349 tmp_path: None,
1350 buf: Vec::new(),
1351 }
1352 }
1353
1354 fn write(&mut self, p: &[u8]) -> io::Result<()> {
1355 self.buf.extend_from_slice(p);
1356 Ok(())
1357 }
1358
1359 fn write_str(&mut self, s: &str) -> io::Result<()> {
1360 self.write(s.as_bytes())
1361 }
1362
1363 fn reader(&mut self) -> io::Result<Box<dyn Read + '_>> {
1364 if self.tmp.is_none() {
1365 return Ok(Box::new(Cursor::new(&self.buf)));
1366 }
1367 self.flush()?;
1368 let mut content = Vec::new();
1369 if let Some(path) = &self.tmp_path {
1370 let mut f = FsFile::open(path)?;
1371 f.read_to_end(&mut content)?;
1372 }
1373 Ok(Box::new(Cursor::new(content)))
1374 }
1375
1376 fn sync(&mut self) -> io::Result<()> {
1377 if self.buf.len() < STREAM_CHUNK_SIZE as usize {
1378 return Ok(());
1379 }
1380 if self.tmp.is_none() {
1381 match create_temp(&self.tmp_dir) {
1382 Ok((f, p)) => {
1383 self.tmp = Some(f);
1384 self.tmp_path = Some(p);
1385 }
1386 Err(_) => return Ok(()),
1387 }
1388 }
1389 self.flush()
1390 }
1391
1392 fn flush(&mut self) -> io::Result<()> {
1393 if let Some(tmp) = &mut self.tmp {
1394 tmp.write_all(&self.buf)?;
1395 tmp.sync_all()?;
1396 self.buf.clear();
1397 }
1398 Ok(())
1399 }
1400
1401 fn close(&mut self) -> io::Result<()> {
1402 self.buf.clear();
1403 if let Some(tmp) = self.tmp.take() {
1404 drop(tmp);
1405 if let Some(path) = self.tmp_path.take() {
1406 let _ = fs::remove_file(path);
1407 }
1408 }
1409 Ok(())
1410 }
1411
1412 fn into_temp_file(&mut self) -> io::Result<Option<PathBuf>> {
1416 self.flush()?;
1417 if let Some(tmp) = self.tmp.take() {
1418 self.buf.clear();
1419 drop(tmp);
1420 return Ok(self.tmp_path.take());
1421 }
1422 if !self.buf.is_empty() {
1423 let (mut f, path) = create_temp(&self.tmp_dir)?;
1424 f.write_all(&self.buf)?;
1425 f.sync_all()?;
1426 drop(f);
1427 self.buf.clear();
1428 return Ok(Some(path));
1429 }
1430 self.buf.clear();
1431 Ok(None)
1432 }
1433}
1434
1435impl Write for BufferedWriter {
1436 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1437 self.buf.extend_from_slice(buf);
1438 Ok(buf.len())
1439 }
1440
1441 fn flush(&mut self) -> io::Result<()> {
1442 self.flush()
1443 }
1444}
1445
1446impl Drop for BufferedWriter {
1447 fn drop(&mut self) {
1448 let _ = self.close();
1449 }
1450}
1451
1452fn create_temp(tmp_dir: &str) -> io::Result<(FsFile, PathBuf)> {
1453 const MAX_RETRIES: usize = 100;
1454
1455 let dir = if tmp_dir.is_empty() {
1456 std::env::temp_dir()
1457 } else {
1458 PathBuf::from(tmp_dir)
1459 };
1460 let now = SystemTime::now()
1461 .duration_since(UNIX_EPOCH)
1462 .unwrap_or_default();
1463 let thread_id = format!("{:?}", thread::current().id())
1464 .chars()
1465 .map(|c| if c.is_alphanumeric() { c } else { '_' })
1466 .collect::<String>();
1467
1468 for _ in 0..MAX_RETRIES {
1469 let path = dir.join(format!(
1470 "excelize-{}-{}-{}-{}.xml",
1471 now.as_secs(),
1472 now.subsec_nanos(),
1473 thread_id,
1474 rand::random::<u32>()
1475 ));
1476 match fs::OpenOptions::new()
1477 .write(true)
1478 .create_new(true)
1479 .open(&path)
1480 {
1481 Ok(file) => return Ok((file, path)),
1482 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
1483 Err(e) => return Err(e),
1484 }
1485 }
1486 Err(io::Error::new(
1487 io::ErrorKind::Other,
1488 "failed to create a unique temporary file after 100 attempts",
1489 ))
1490}
1491
1492fn escape_xml(s: &str) -> String {
1497 escape(s).into_owned()
1498}
1499
1500fn io_err(s: String) -> Box<dyn std::error::Error + Send + Sync> {
1501 Box::new(io::Error::new(io::ErrorKind::InvalidData, s))
1502}
1503
1504fn needs_space_preserve(s: &str) -> bool {
1505 if s.is_empty() {
1506 return false;
1507 }
1508 let prefix = s.as_bytes().first().copied().unwrap_or(0);
1509 let suffix = s.as_bytes().last().copied().unwrap_or(0);
1510 [b' ', b'\t', b'\n', b'\r'].contains(&prefix) || [b' ', b'\t', b'\n', b'\r'].contains(&suffix)
1511}
1512
1513fn format_float(value: f64) -> String {
1514 let s = format!("{value:.15}");
1515 if s.contains('.') {
1516 s.trim_end_matches('0').trim_end_matches('.').to_string()
1517 } else {
1518 s
1519 }
1520}
1521
1522#[cfg(test)]
1523mod tests {
1524 use super::*;
1525 use crate::options::Options;
1526 use chrono::{NaiveDate, NaiveTime};
1527
1528 #[test]
1529 fn stream_writer_basic_round_trip() {
1530 let mut f = File::new_with_options(Options::default());
1531 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1532
1533 sw.set_row(
1534 "A1",
1535 &[
1536 &CellValue::String("Data".to_string()),
1537 &CellValue::Int(42),
1538 &CellValue::Float(3.14),
1539 &CellValue::Bool(true),
1540 ],
1541 None,
1542 )
1543 .unwrap();
1544 sw.set_row(
1545 "A2",
1546 &[&CellValue::Int(1), &CellValue::Int(2)],
1547 Some(RowOpts::default()),
1548 )
1549 .unwrap();
1550 sw.flush().unwrap();
1551 drop(sw);
1552
1553 let tmp = std::env::temp_dir().join("excelize_stream_basic_test.xlsx");
1554 f.save_as(tmp.to_str().unwrap()).unwrap();
1555
1556 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1557 assert_eq!(f2.get_cell_value("Sheet1", "A1").unwrap(), "Data");
1558 assert_eq!(f2.get_cell_value("Sheet1", "B1").unwrap(), "42");
1559 assert_eq!(f2.get_cell_value("Sheet1", "C1").unwrap(), "3.14");
1560 assert_eq!(f2.get_cell_value("Sheet1", "D1").unwrap(), "TRUE");
1561 assert_eq!(f2.get_cell_value("Sheet1", "A2").unwrap(), "1");
1562 let _ = std::fs::remove_file(&tmp);
1563 }
1564
1565 #[test]
1566 fn stream_writer_cell_with_style_and_formula() {
1567 let mut f = File::new_with_options(Options::default());
1568 let style_id = f
1569 .new_style(&Style {
1570 font: Some(crate::styles::Font {
1571 color: Some("777777".to_string()),
1572 ..Default::default()
1573 }),
1574 ..Default::default()
1575 })
1576 .unwrap();
1577
1578 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1579 sw.set_row(
1580 "A1",
1581 &[
1582 &Cell {
1583 style_id,
1584 value: Some(CellValue::String("Styled".to_string())),
1585 ..Default::default()
1586 },
1587 &Cell {
1588 formula: "SUM(A1,A1)".to_string(),
1589 value: Some(CellValue::String("formula value".to_string())),
1590 ..Default::default()
1591 },
1592 ],
1593 None,
1594 )
1595 .unwrap();
1596 sw.flush().unwrap();
1597 drop(sw);
1598
1599 let tmp = std::env::temp_dir().join("excelize_stream_formula_test.xlsx");
1600 f.save_as(tmp.to_str().unwrap()).unwrap();
1601 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1602 assert_eq!(f2.get_cell_value("Sheet1", "A1").unwrap(), "Styled");
1603 assert_eq!(f2.get_cell_value("Sheet1", "B1").unwrap(), "formula value");
1604 let _ = std::fs::remove_file(&tmp);
1605 }
1606
1607 #[test]
1608 fn stream_writer_row_and_col_options() {
1609 let mut f = File::new_with_options(Options::default());
1610 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1611 sw.set_col_width(1, 3, 20.0).unwrap();
1612 sw.set_col_visible(2, 2, false).unwrap();
1613 sw.set_row(
1614 "A1",
1615 &[&CellValue::Int(1)],
1616 Some(RowOpts {
1617 height: 30.0,
1618 hidden: false,
1619 style_id: 0,
1620 outline_level: 2,
1621 }),
1622 )
1623 .unwrap();
1624 sw.flush().unwrap();
1625 drop(sw);
1626
1627 let tmp = std::env::temp_dir().join("excelize_stream_options_test.xlsx");
1628 f.save_as(tmp.to_str().unwrap()).unwrap();
1629 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1630 assert_eq!(f2.get_row_height("Sheet1", 1).unwrap(), 30.0);
1631 assert!(!f2.get_col_visible("Sheet1", "B").unwrap());
1632 assert_eq!(f2.get_col_width("Sheet1", "A").unwrap(), 20.0);
1633 let _ = std::fs::remove_file(&tmp);
1634 }
1635
1636 #[test]
1637 fn stream_writer_merge_cell() {
1638 let mut f = File::new_with_options(Options::default());
1639 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1640 sw.set_row("A1", &[&CellValue::Int(1)], None).unwrap();
1641 sw.merge_cell("A1", "B2").unwrap();
1642 sw.flush().unwrap();
1643 drop(sw);
1644
1645 let tmp = std::env::temp_dir().join("excelize_stream_merge_test.xlsx");
1646 f.save_as(tmp.to_str().unwrap()).unwrap();
1647 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1648 let ws = f2.work_sheet_reader("Sheet1").unwrap();
1649 assert!(ws.merge_cells.is_some());
1650 let _ = std::fs::remove_file(&tmp);
1651 }
1652
1653 #[test]
1654 fn stream_writer_date_and_time() {
1655 let mut f = File::new_with_options(Options::default());
1656 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1657 let dt = NaiveDate::from_ymd_opt(2024, 7, 13)
1658 .unwrap()
1659 .and_hms_opt(12, 30, 0)
1660 .unwrap();
1661 sw.set_row(
1662 "A1",
1663 &[
1664 &CellValue::DateTime(dt),
1665 &CellValue::Time(NaiveTime::from_hms_opt(14, 30, 0).unwrap()),
1666 ],
1667 None,
1668 )
1669 .unwrap();
1670 sw.flush().unwrap();
1671 drop(sw);
1672
1673 let tmp = std::env::temp_dir().join("excelize_stream_date_test.xlsx");
1674 f.save_as(tmp.to_str().unwrap()).unwrap();
1675 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1676 assert_eq!(f2.get_cell_value("Sheet1", "A1").unwrap(), "7/13/24 12:30");
1677 assert_eq!(f2.get_cell_value("Sheet1", "B1").unwrap(), "14:30:00");
1678 let _ = std::fs::remove_file(&tmp);
1679 }
1680
1681 #[test]
1682 fn stream_writer_row_order_error() {
1683 let f = File::new_with_options(Options::default());
1684 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1685 sw.set_row("A2", &[&CellValue::Int(1)], None).unwrap();
1686 assert!(sw.set_row("A1", &[&CellValue::Int(1)], None).is_err());
1687 }
1688
1689 #[test]
1690 fn stream_writer_invalid_column_errors() {
1691 let f = File::new_with_options(Options::default());
1692 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1693 assert!(sw.set_col_width(0, 1, 10.0).is_err());
1694 assert!(sw.set_col_width(1, 1, 300.0).is_err());
1695 assert!(sw.set_col_outline_level(1, 8).is_err());
1696 }
1697
1698 #[test]
1699 fn stream_writer_mixed_types_and_nil_values() {
1700 let mut f = File::new_with_options(Options::default());
1701 let style_id = f
1702 .new_style(&Style {
1703 font: Some(crate::styles::Font {
1704 color: Some("777777".to_string()),
1705 ..Default::default()
1706 }),
1707 ..Default::default()
1708 })
1709 .unwrap();
1710
1711 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1712 sw.set_row(
1713 "A1",
1714 &[
1715 &1i32 as &dyn StreamCellValue,
1716 &2.5f64,
1717 &"hello",
1718 &true,
1719 &None::<CellValue>,
1720 &Cell {
1721 style_id,
1722 value: None,
1723 ..Default::default()
1724 },
1725 &Cell {
1726 value: Some(CellValue::String("with value".to_string())),
1727 ..Default::default()
1728 },
1729 ],
1730 None,
1731 )
1732 .unwrap();
1733 sw.flush().unwrap();
1734 drop(sw);
1735
1736 let tmp = std::env::temp_dir().join("excelize_stream_mixed_test.xlsx");
1737 f.save_as(tmp.to_str().unwrap()).unwrap();
1738 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1739 assert_eq!(f2.get_cell_value("Sheet1", "A1").unwrap(), "1");
1740 assert_eq!(f2.get_cell_value("Sheet1", "B1").unwrap(), "2.5");
1741 assert_eq!(f2.get_cell_value("Sheet1", "C1").unwrap(), "hello");
1742 assert_eq!(f2.get_cell_value("Sheet1", "D1").unwrap(), "TRUE");
1743 assert_eq!(f2.get_cell_value("Sheet1", "E1").unwrap(), "");
1744 assert_eq!(f2.get_cell_value("Sheet1", "F1").unwrap(), "");
1745 assert_eq!(f2.get_cell_value("Sheet1", "G1").unwrap(), "with value");
1746 assert_eq!(f2.get_cell_style("Sheet1", "F1").unwrap(), style_id);
1747 let _ = std::fs::remove_file(&tmp);
1748 }
1749
1750 #[test]
1751 fn stream_writer_empty_string_is_written() {
1752 let mut f = File::new_with_options(Options::default());
1753 let mut sw = f.new_stream_writer("Sheet1").unwrap();
1754 sw.set_row(
1755 "A1",
1756 &[&CellValue::String("".to_string()) as &dyn StreamCellValue],
1757 None,
1758 )
1759 .unwrap();
1760 sw.flush().unwrap();
1761 drop(sw);
1762
1763 let tmp = std::env::temp_dir().join("excelize_stream_empty_str_test.xlsx");
1764 f.save_as(tmp.to_str().unwrap()).unwrap();
1765 let f2 = File::open_file(tmp.to_str().unwrap(), Options::default()).unwrap();
1766 assert_eq!(f2.get_cell_value("Sheet1", "A1").unwrap(), "");
1767 let _ = std::fs::remove_file(&tmp);
1768 }
1769}