//! 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