//! Writing a workbook as an XLSX package. use std::collections::{BTreeMap, HashMap}; use std::fmt::Write as _; use gridcraft_core::RangeRef; use gridcraft_formula::quote_sheet; use gridcraft_model::{CalcMode, Style, Visibility, Workbook}; use crate::IoError; use crate::package::ZipOut; use crate::xml::{esc, esc_attr}; pub const NS_MAIN: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; pub const NS_REL: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; pub const XML_DECL: &str = "\n"; pub const CT_WORKSHEET: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; pub const CT_DRAWING: &str = "application/vnd.openxmlformats-officedocument.drawing+xml"; pub const CT_CHART: &str = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"; pub const CT_TABLE: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"; pub const CT_COMMENTS: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"; /// Main workbook part: macro-free and macro-enabled variants (Excel refuses a `.xlsm` whose main /// part uses the macro-free content type, and vice versa). pub const CT_WORKBOOK_MAIN: &str = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; pub const CT_WORKBOOK_MAIN_MACRO: &str = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"; pub const CT_VBA: &str = "application/vnd.ms-office.vbaProject"; pub const CT_THREADED_COMMENTS: &str = "application/vnd.ms-excel.threadedcomments+xml"; pub const CT_PERSONS: &str = "application/vnd.ms-excel.person+xml"; pub const REL_THREADED_COMMENT: &str = "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment"; pub const REL_PERSON: &str = "http://schemas.microsoft.com/office/2017/10/relationships/person"; /// Namespace of the threaded comments and persons parts ([MS-XLSX]). pub const NS_THREADED: &str = "http://schemas.microsoft.com/office/spreadsheetml/2018/threadedcomments"; /// A part's relationships. #[derive(Default)] pub struct Rels { items: Vec<(String, String, String, bool)>, } impl Rels { /// Adds a relationship of type `kind` (last segment, e.g. `worksheet`); returns its id. pub fn add(&mut self, kind: &str, target: &str) -> String { let id = format!("rId{}", self.items.len() + 1); let ty = match kind { "core-properties" => "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties".to_string(), // The VBA project relationship lives in the Microsoft-documented namespace, not the // OpenXML one; Excel rejects the file if this type is wrong. "vbaProject" => "http://schemas.microsoft.com/office/2006/relationships/vbaProject".to_string(), k => format!("{NS_REL}/{k}"), }; self.items.push((id.clone(), ty, target.to_string(), false)); id } pub fn add_external(&mut self, kind: &str, target: &str) -> String { let id = format!("rId{}", self.items.len() + 1); self.items.push((id.clone(), format!("{NS_REL}/{kind}"), target.to_string(), true)); id } /// Adds a Microsoft extension relationship with its complete namespace URI. pub fn add_uri(&mut self, ty: &str, target: &str) -> String { let id = format!("rId{}", self.items.len() + 1); self.items.push((id.clone(), ty.to_string(), target.to_string(), false)); id } /// Adds a relationship with its complete type URI, internal or external (kept content). pub fn add_raw(&mut self, ty: &str, target: &str, external: bool) -> String { let id = format!("rId{}", self.items.len() + 1); self.items.push((id.clone(), crate::xml::esc_attr(ty), target.to_string(), external)); id } pub fn is_empty(&self) -> bool { self.items.is_empty() } pub fn xml(&self) -> String { let mut s = format!("{XML_DECL}"); for (id, ty, target, ext) in &self.items { let _ = write!( s, "", esc_attr(target), if *ext { " TargetMode=\"External\"" } else { "" } ); } s.push_str(""); s } } /// Shared string table being built. #[derive(Default)] pub struct Sst { list: Vec, map: HashMap, pub count: u64, } impl Sst { pub fn index(&mut self, s: &str) -> u32 { self.count += 1; if let Some(&i) = self.map.get(s) { return i; } let i = self.list.len() as u32; self.list.push(s.to_string()); self.map.insert(s.to_string(), i); i } } /// Package being assembled. pub struct Out { parts: Vec<(String, Vec)>, overrides: Vec<(String, String)>, defaults: BTreeMap, pub dxfs: Vec