use crate::cell::Value;
use crate::error::Result;
use crate::reference::CellRef;
use crate::shared_strings::SharedStrings;
use crate::workbook::Workbook;
use crate::worksheet::Worksheet;
use crate::xml;
use quick_xml::events::{BytesEnd, BytesStart, Event};
pub fn write<W: std::io::Write>(
sink: W,
wb: &Workbook,
ws: &Worksheet,
strings: &mut SharedStrings,
) -> Result<()> {
let mut writer = xml::writer(sink);
let mut root = BytesStart::new("worksheet");
root.push_attribute(("xmlns", xml::NS_MAIN));
root.push_attribute(("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"));
writer.write_event(Event::Start(root))?;
write_sheet_views(&mut writer, ws)?;
write_sheet_format_pr(&mut writer)?;
write_cols(&mut writer, ws)?;
write_sheet_data(&mut writer, wb, ws, strings)?;
write_merged_cells(&mut writer, ws)?;
write_page_margins(&mut writer)?;
writer.write_event(Event::End(BytesEnd::new("worksheet")))?;
Ok(())
}
fn write_sheet_views<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, _ws: &Worksheet) -> Result<()> {
let views = BytesStart::new("sheetViews");
writer.write_event(Event::Start(views))?;
writer.write_event(Event::Empty(BytesStart::new("sheetView")))?;
writer.write_event(Event::End(BytesEnd::new("sheetViews")))?;
Ok(())
}
fn write_sheet_format_pr<W: std::io::Write>(writer: &mut quick_xml::Writer<W>) -> Result<()> {
let mut elem = BytesStart::new("sheetFormatPr");
elem.push_attribute(("defaultRowHeight", "15"));
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_cols<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, ws: &Worksheet) -> Result<()> {
if ws.col_widths.is_empty() && ws.hidden_cols.is_empty() {
return Ok(());
}
let mut all_cols: std::collections::BTreeSet<u32> = ws.col_widths.keys().copied().collect();
all_cols.extend(ws.hidden_cols.iter().copied());
writer.write_event(Event::Start(BytesStart::new("cols")))?;
for col in all_cols {
let mut elem = BytesStart::new("col");
elem.push_attribute(("min", col.to_string().as_str()));
elem.push_attribute(("max", col.to_string().as_str()));
if let Some(width) = ws.col_widths.get(&col) {
elem.push_attribute(("width", width.to_string().as_str()));
elem.push_attribute(("customWidth", "1"));
}
if ws.hidden_cols.contains(&col) {
elem.push_attribute(("hidden", "1"));
}
writer.write_event(Event::Empty(elem))?;
}
writer.write_event(Event::End(BytesEnd::new("cols")))?;
Ok(())
}
fn write_sheet_data<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
wb: &Workbook,
ws: &Worksheet,
strings: &mut SharedStrings,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("sheetData")))?;
let mut rows: std::collections::BTreeMap<u32, Vec<(u32, &crate::cell::Cell)>> = std::collections::BTreeMap::new();
for ((row, col), cell) in &ws.cells {
rows.entry(*row).or_default().push((*col, cell));
}
for (row_idx, cols) in rows {
let mut row_start = BytesStart::new("row");
row_start.push_attribute(("r", row_idx.to_string().as_str()));
if let Some(ht) = ws.row_heights.get(&row_idx) {
row_start.push_attribute(("ht", ht.to_string().as_str()));
row_start.push_attribute(("customHeight", "1"));
}
if ws.hidden_rows.contains(&row_idx) {
row_start.push_attribute(("hidden", "1"));
}
writer.write_event(Event::Start(row_start))?;
for (col_idx, cell) in cols {
write_cell(writer, wb, ws, row_idx, col_idx, cell, strings)?;
}
writer.write_event(Event::End(BytesEnd::new("row")))?;
}
writer.write_event(Event::End(BytesEnd::new("sheetData")))?;
Ok(())
}
fn write_cell<W: std::io::Write>(
writer: &mut quick_xml::Writer<W>,
_wb: &Workbook,
_ws: &Worksheet,
row: u32,
col: u32,
cell: &crate::cell::Cell,
strings: &mut SharedStrings,
) -> Result<()> {
if cell.value.is_empty() && cell.style_id.is_none() {
return Ok(());
}
let mut elem = BytesStart::new("c");
let reference = CellRef::new(row, col).map(|r| r.to_a1()).unwrap_or_default();
elem.push_attribute(("r", reference.as_str()));
if let Some(style_id) = cell.style_id {
elem.push_attribute(("s", style_id.index().to_string().as_str()));
}
match &cell.value {
Value::Empty => {
writer.write_event(Event::Empty(elem))?;
}
Value::Number(n) => {
elem.push_attribute(("t", "n"));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("v")))?;
xml::write_text(writer, &n.to_string())?;
writer.write_event(Event::End(BytesEnd::new("v")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
Value::String(s) => {
let idx = strings.add(s);
elem.push_attribute(("t", "s"));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("v")))?;
xml::write_text(writer, &idx.to_string())?;
writer.write_event(Event::End(BytesEnd::new("v")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
Value::Bool(b) => {
elem.push_attribute(("t", "b"));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("v")))?;
xml::write_text(writer, if *b { "1" } else { "0" })?;
writer.write_event(Event::End(BytesEnd::new("v")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
Value::Formula(f) => {
let formula_text = f.strip_prefix('=').unwrap_or(f.as_str());
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("f")))?;
xml::write_text(writer, formula_text)?;
writer.write_event(Event::End(BytesEnd::new("f")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
Value::Error(e) => {
elem.push_attribute(("t", "e"));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("v")))?;
xml::write_text(writer, e)?;
writer.write_event(Event::End(BytesEnd::new("v")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
Value::Date(d) => {
elem.push_attribute(("t", "n"));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Start(BytesStart::new("v")))?;
xml::write_text(writer, &d.to_string())?;
writer.write_event(Event::End(BytesEnd::new("v")))?;
writer.write_event(Event::End(BytesEnd::new("c")))?;
}
}
Ok(())
}
fn write_merged_cells<W: std::io::Write>(writer: &mut quick_xml::Writer<W>, ws: &Worksheet) -> Result<()> {
if ws.merged_cells.is_empty() {
return Ok(());
}
let mut start = BytesStart::new("mergeCells");
start.push_attribute(("count", ws.merged_cells.len().to_string().as_str()));
writer.write_event(Event::Start(start))?;
for range in &ws.merged_cells {
let mut elem = BytesStart::new("mergeCell");
elem.push_attribute(("ref", range.to_a1().as_str()));
writer.write_event(Event::Empty(elem))?;
}
writer.write_event(Event::End(BytesEnd::new("mergeCells")))?;
Ok(())
}
fn write_page_margins<W: std::io::Write>(writer: &mut quick_xml::Writer<W>) -> Result<()> {
let mut elem = BytesStart::new("pageMargins");
elem.push_attribute(("left", "0.7"));
elem.push_attribute(("right", "0.7"));
elem.push_attribute(("top", "0.75"));
elem.push_attribute(("bottom", "0.75"));
elem.push_attribute(("header", "0.3"));
elem.push_attribute(("footer", "0.3"));
writer.write_event(Event::Empty(elem))?;
Ok(())
}