mod _rels; mod doc_props; mod escape; mod shared_strings; mod styles; mod workbook; mod workbook_xml_rels; mod worksheets; mod xml_constants; use std::io::BufWriter; use std::{ fs, io::{Seek, Write}, }; use ironcalc_base::expressions::utils::number_to_column; use ironcalc_base::types::Workbook; use ironcalc_base::{get_milliseconds_since_epoch, Model}; use self::xml_constants::XML_DECLARATION; use crate::error::XlsxError; #[cfg(test)] mod test; fn get_content_types_xml(workbook: &Workbook) -> String { // A list of all files in the zip let mut content = vec![ r#""#.to_string(), r#""#.to_string(), r#""#.to_string(), r#""#.to_string(), ]; for worksheet in 0..workbook.worksheets.len() { let sheet = format!( r#""#, worksheet + 1 ); content.push(sheet); } // we skip the theme and calcChain // r#""#, // r#""#, content.extend([ r#""#.to_string(), r#""#.to_string(), r#""#.to_string(), r#""#.to_string(), r#""#.to_string(), ]); format!("{XML_DECLARATION}\n{}", content.join("")) } /// Exports a model to an xlsx file pub fn save_to_xlsx(model: &Model, file_name: &str) -> Result<(), XlsxError> { let file_path = std::path::Path::new(&file_name); if file_path.exists() { return Err(XlsxError::IO(format!("file {} already exists", file_name))); } let file = fs::File::create(file_path).unwrap(); let writer = BufWriter::new(file); save_xlsx_to_writer(model, writer)?; Ok(()) } pub fn save_xlsx_to_writer(model: &Model, writer: W) -> Result { let workbook = &model.workbook; let mut zip = zip::ZipWriter::new(writer); let options = zip::write::FileOptions::default(); // root folder zip.start_file("[Content_Types].xml", options)?; zip.write_all(get_content_types_xml(workbook).as_bytes())?; zip.add_directory("docProps", options)?; zip.start_file("docProps/app.xml", options)?; zip.write_all(doc_props::get_app_xml(workbook).as_bytes())?; zip.start_file("docProps/core.xml", options)?; let milliseconds = get_milliseconds_since_epoch(); zip.write_all(doc_props::get_core_xml(workbook, milliseconds)?.as_bytes())?; // Package-relationship item zip.add_directory("_rels", options)?; zip.start_file("_rels/.rels", options)?; zip.write_all(_rels::get_dot_rels(workbook).as_bytes())?; zip.add_directory("xl", options)?; zip.start_file("xl/sharedStrings.xml", options)?; zip.write_all(shared_strings::get_shared_strings_xml(workbook).as_bytes())?; zip.start_file("xl/styles.xml", options)?; zip.write_all(styles::get_styles_xml(workbook).as_bytes())?; zip.start_file("xl/workbook.xml", options)?; zip.write_all(workbook::get_workbook_xml(workbook).as_bytes())?; zip.add_directory("xl/_rels", options)?; zip.start_file("xl/_rels/workbook.xml.rels", options)?; zip.write_all(workbook_xml_rels::get_workbook_xml_rels(workbook).as_bytes())?; zip.add_directory("xl/worksheets", options)?; for (sheet_index, worksheet) in workbook.worksheets.iter().enumerate() { let id = sheet_index + 1; zip.start_file(&format!("xl/worksheets/sheet{id}.xml"), options)?; let dimension = model .workbook .worksheet(sheet_index as u32) .unwrap() .dimension(); let column_min_str = number_to_column(dimension.min_column).unwrap(); let column_max_str = number_to_column(dimension.max_column).unwrap(); let min_row = dimension.min_row; let max_row = dimension.max_row; let sheet_dimension_str = &format!("{column_min_str}{min_row}:{column_max_str}{max_row}"); zip.write_all( worksheets::get_worksheet_xml( worksheet, &model.parsed_formulas[sheet_index], sheet_dimension_str, ) .as_bytes(), )?; } let writer = zip.finish()?; Ok(writer) } /// Exports an internal representation of a workbook into an equivalent IronCalc json format pub fn save_to_icalc(workbook: Workbook, output: &str) { let s = bitcode::encode(&workbook); let file_path = std::path::Path::new(output); let mut file = fs::File::create(file_path).unwrap(); file.write_all(&s).unwrap(); }