#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
use std::collections::HashMap;
use itertools::Itertools;
use ironcalc_base::{
expressions::{
parser::{stringify::to_excel_string, Node},
types::CellReferenceRC,
utils::number_to_column,
},
types::{Cell, Worksheet},
};
use super::{escape::escape_xml, xml_constants::XML_DECLARATION};
fn get_cell_style_attribute(s: i32) -> String {
if s == 0 {
"".to_string()
} else {
format!(" s=\"{s}\"")
}
}
fn get_formula_attribute(
sheet_name: String,
row: i32,
column: i32,
parsed_formula: &Node,
) -> String {
let cell_ref = CellReferenceRC {
sheet: sheet_name,
row,
column,
};
let formula = &to_excel_string(parsed_formula, &cell_ref);
escape_xml(formula).to_string()
}
pub(crate) fn get_worksheet_xml(
worksheet: &Worksheet,
parsed_formulas: &[Node],
dimension: &str,
is_sheet_selected: bool,
) -> String {
let mut sheet_data_str: Vec<String> = vec![];
let mut cols_str: Vec<String> = vec![];
let mut merged_cells_str: Vec<String> = vec![];
for col in &worksheet.cols {
let min = col.min;
let max = col.max;
let width = col.width;
let custom_width = i32::from(col.custom_width);
let column_style = match col.style {
Some(s) => format!(" style=\"{s}\""),
None => "".to_string(),
};
cols_str.push(format!(
"<col min=\"{min}\" max=\"{max}\" width=\"{width}\" customWidth=\"{custom_width}\"{column_style}/>"
));
}
let mut row_style_dict = HashMap::new();
for row in &worksheet.rows {
row_style_dict.insert(row.r, row.clone());
}
for (row_index, row_data) in worksheet.sheet_data.iter().sorted_by_key(|x| x.0) {
let mut row_data_str: Vec<String> = vec![];
for (column_index, cell) in row_data.iter().sorted_by_key(|x| x.0) {
let column_name = number_to_column(*column_index).unwrap();
let cell_name = format!("{column_name}{row_index}");
match cell {
Cell::EmptyCell { s } => {
let style = get_cell_style_attribute(*s);
row_data_str.push(format!("<c r=\"{cell_name}\"{style}/>"));
}
Cell::BooleanCell { v, s } => {
let b = i32::from(*v);
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"b\"{style}><v>{b}</v></c>"
));
}
Cell::NumberCell { v, s } => {
let style = get_cell_style_attribute(*s);
row_data_str.push(format!("<c r=\"{cell_name}\"{style}><v>{v}</v></c>"));
}
Cell::ErrorCell { ei, s } => {
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"e\"{style}><v>{ei}</v></c>"
));
}
Cell::SharedString { si, s } => {
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"s\"{style}><v>{si}</v></c>"
));
}
Cell::CellFormula { f: _, s: _ } => {
panic!("Model needs to be evaluated before saving!");
}
Cell::CellFormulaBoolean { f, v, s } => {
let style = get_cell_style_attribute(*s);
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
);
let b = i32::from(*v);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"b\"{style}><f>{formula}</f><v>{b}</v></c>"
));
}
Cell::CellFormulaNumber { f, v, s } => {
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
);
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\"{style}><f>{formula}</f><v>{v}</v></c>"
));
}
Cell::CellFormulaString { f, v, s } => {
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
);
let style = get_cell_style_attribute(*s);
let escaped_v = escape_xml(v);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"str\"{style}><f>{formula}</f><v>{escaped_v}</v></c>"
));
}
Cell::CellFormulaError {
f,
ei,
s,
o: _,
m: _,
} => {
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
);
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"e\"{style}><f>{formula}</f><v>{ei}</v></c>"
));
}
}
}
let row_style_str = match row_style_dict.get(row_index) {
Some(row_style) => {
let hidden_str = if row_style.hidden {
r#" hidden="1""#
} else {
""
};
format!(
r#" s="{}" ht="{}" customHeight="{}" customFormat="{}"{}"#,
row_style.s,
row_style.height,
i32::from(row_style.custom_height),
i32::from(row_style.custom_format),
hidden_str,
)
}
None => "".to_string(),
};
sheet_data_str.push(format!(
"<row r=\"{row_index}\"{row_style_str}>{}</row>",
row_data_str.join("")
))
}
let sheet_data = sheet_data_str.join("");
for merge_cell_ref in &worksheet.merge_cells {
merged_cells_str.push(format!("<mergeCell ref=\"{merge_cell_ref}\"/>"))
}
let merged_cells_count = merged_cells_str.len();
let cols = cols_str.join("");
let cols = if cols.is_empty() {
"".to_string()
} else {
format!("<cols>{cols}</cols>")
};
let tab_selected = if is_sheet_selected {
" tabSelected=\"1\""
} else {
""
};
let show_grid_lines = if !worksheet.show_grid_lines {
" showGridLines=\"0\""
} else {
""
};
let mut active_cell = "A1".to_string();
let mut sqref = "A1".to_string();
let views = &worksheet.views;
if let Some(view) = views.get(&0) {
let range = view.range;
let row = view.row;
let column = view.column;
let column_name = number_to_column(column).unwrap_or("A".to_string());
active_cell = format!("{column_name}{row}");
let column_start = number_to_column(range[1]).unwrap_or("A".to_string());
let column_end = number_to_column(range[3]).unwrap_or("A".to_string());
if range[0] == range[2] && range[1] == range[3] {
sqref = format!("{column_start}{}", range[0]);
} else {
sqref = format!("{}{}:{}{}", column_start, range[0], column_end, range[2]);
}
}
let merge_cells_section = if merged_cells_count > 0 {
format!(
"<mergeCells count=\"{}\">{}</mergeCells>",
merged_cells_count,
merged_cells_str.join("")
)
} else {
"".to_string()
};
let frozen_rows = worksheet.frozen_rows;
let frozen_columns = worksheet.frozen_columns;
let pane = if frozen_rows > 0 && frozen_columns > 0 {
let first_column = number_to_column(frozen_columns + 1).unwrap_or("A".to_string());
let top_left_cell = format!("{}{}", first_column, frozen_rows + 1);
let top_right_active_cell = format!("{first_column}1");
let bottom_left_active_cell = format!("A{}", frozen_rows + 1);
format!(
"<pane xSplit=\"{frozen_columns}\" ySplit=\"{frozen_rows}\" topLeftCell=\"{top_left_cell}\" activePane=\"bottomRight\" state=\"frozen\"/>\
<selection pane=\"topRight\" activeCell=\"{top_right_active_cell}\" sqref=\"{top_right_active_cell}\"/>\
<selection pane=\"bottomLeft\" activeCell=\"{bottom_left_active_cell}\" sqref=\"{bottom_left_active_cell}\"/>\
<selection pane=\"bottomRight\" activeCell=\"{active_cell}\" sqref=\"{sqref}\"/>",
)
} else if frozen_rows > 0 {
let top_left_cell = format!("A{}", frozen_rows + 1);
format!(
"<pane ySplit=\"{frozen_rows}\" topLeftCell=\"{top_left_cell}\" activePane=\"bottomLeft\" state=\"frozen\"/>\
<selection pane=\"bottomLeft\" activeCell=\"{active_cell}\" sqref=\"{sqref}\"/>",
)
} else if frozen_columns > 0 {
let top_left_cell = format!(
"{}1",
number_to_column(frozen_columns + 1).unwrap_or("A".to_string())
);
format!(
"<pane xSplit=\"{frozen_columns}\" topLeftCell=\"{top_left_cell}\" activePane=\"topRight\" state=\"frozen\"/>\
<selection pane=\"topRight\" activeCell=\"{active_cell}\" sqref=\"{sqref}\"/>"
)
} else {
format!(r#"<selection activeCell="{active_cell}" sqref="{sqref}"/>"#)
};
format!(
"{XML_DECLARATION}\
<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\
<dimension ref=\"{dimension}\"/>\
<sheetViews>\
<sheetView workbookViewId=\"0\"{show_grid_lines}{tab_selected}>\
{pane}\
</sheetView>\
</sheetViews>\
{cols}\
<sheetData>\
{sheet_data}\
</sheetData>\
{merge_cells_section}\
</worksheet>"
)
}