use super::common::workbook_not_found;
use crate::store::WorkbookStore;
use crate::types::enums::{ChartType, LegendPosition};
use crate::types::inputs::*;
use crate::types::responses::*;
fn find_sheet(wb: &zavora_xlsx::Workbook, name: &str) -> Option<usize> {
wb.sheet_names().iter().position(|n| *n == name)
}
fn sheet_err(name: &str) -> String {
error(
ErrorCategory::NotFound,
&format!("Sheet '{}' not found", name),
"Check sheet name.",
)
}
pub fn set_page_setup(
store: &mut WorkbookStore,
input: SetPageSetupInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if input.landscape == Some(true) {
ws.set_landscape();
} else if input.landscape == Some(false) {
ws.set_portrait();
}
if let Some(ps) = input.paper_size {
ws.set_paper_size(ps);
}
if let Some(ref m) = input.margins {
ws.set_margins(
m.top.unwrap_or(0.75),
m.bottom.unwrap_or(0.75),
m.left.unwrap_or(0.7),
m.right.unwrap_or(0.7),
);
}
if let Some(ref f) = input.fit_to_pages {
ws.set_fit_to_page(f.width, f.height);
}
if let Some(s) = input.print_scale {
ws.set_print_scale(s);
}
if let Some(ref pa) = input.print_area {
let (r1, c1, r2, c2) =
zavora_xlsx::utility::parse_range_ref(pa).map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_print_area(r1, c1, r2, c2);
}
if let Some(ref rr) = input.repeat_rows {
ws.set_repeat_rows(rr.first, rr.last);
}
if let Some(ref h) = input.header {
ws.set_header(h);
}
if let Some(ref f) = input.footer {
ws.set_footer(f);
}
if let Some(true) = input.print_gridlines {
ws.set_print_settings(&zavora_xlsx::PrintSettings::new().print_gridlines(true));
}
if input.center_horizontally == Some(true) {
ws.set_print_settings(&zavora_xlsx::PrintSettings::new().center_horizontally(true));
}
Ok(success_no_data("Page setup configured"))
}
pub fn add_comment(
store: &mut WorkbookStore,
input: AddCommentInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(ref author) = input.author {
ws.add_comment_with_author(row, col, &input.text, author);
} else {
ws.add_comment(row, col, &input.text);
}
Ok(success_no_data(&format!("Comment added at {}", input.cell)))
}
pub fn add_hyperlink(
store: &mut WorkbookStore,
input: AddHyperlinkInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let display = input.display_text.as_deref().unwrap_or(&input.url);
ws.write(row, col, display)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.write_url(
row,
col,
&input.url,
input.display_text.as_deref().unwrap_or(&input.url),
)?;
Ok(success_no_data(&format!(
"Hyperlink added at {}",
input.cell
)))
}
pub fn add_defined_name(
store: &mut WorkbookStore,
input: AddDefinedNameInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
entry.data.define_name(&input.name, &input.formula);
Ok(success_no_data(&format!(
"Defined name '{}' added",
input.name
)))
}
pub fn list_defined_names(
store: &mut WorkbookStore,
input: ListDefinedNamesInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let names: Vec<serde_json::Value> = entry
.data
.defined_names()
.iter()
.map(|(n, f)| serde_json::json!({"name": n, "formula": f}))
.collect();
Ok(success("Defined names listed", names))
}
pub fn set_sheet_settings(
store: &mut WorkbookStore,
input: SetSheetSettingsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if input.hidden == Some(true) {
ws.set_hidden();
}
if input.very_hidden == Some(true) {
ws.set_very_hidden();
}
if let Some(z) = input.zoom {
ws.set_zoom(z);
}
if input.hide_gridlines == Some(true) {
ws.hide_gridlines();
}
if input.hide_headings == Some(true) {
ws.hide_headings();
}
if let Some(ref c) = input.tab_color {
ws.set_tab_color(c.as_str());
}
if input.right_to_left == Some(true) {
ws.set_right_to_left();
}
Ok(success_no_data("Sheet settings updated"))
}
pub fn set_active_sheet(
store: &mut WorkbookStore,
input: SetActiveSheetInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
entry.data.set_active_sheet(input.sheet_index);
Ok(success_no_data(&format!(
"Active sheet set to index {}",
input.sheet_index
)))
}
pub fn insert_rows(
store: &mut WorkbookStore,
input: InsertDeleteRowsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_rows(input.at_row.saturating_sub(1), input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"{} rows inserted at row {}",
input.count, input.at_row
)))
}
pub fn delete_rows(
store: &mut WorkbookStore,
input: InsertDeleteRowsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.remove_rows(input.at_row.saturating_sub(1), input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"{} rows deleted at row {}",
input.count, input.at_row
)))
}
pub fn insert_columns(
store: &mut WorkbookStore,
input: InsertDeleteColumnsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col = zavora_xlsx::utility::col_from_letter(&input.at_column)
.map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_columns(col, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"{} columns inserted at {}",
input.count, input.at_column
)))
}
pub fn delete_columns(
store: &mut WorkbookStore,
input: InsertDeleteColumnsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col = zavora_xlsx::utility::col_from_letter(&input.at_column)
.map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.remove_columns(col, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"{} columns deleted at {}",
input.count, input.at_column
)))
}
pub fn group_rows(
store: &mut WorkbookStore,
input: GroupRowsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.group_rows(
input.start.saturating_sub(1),
input.end.saturating_sub(1),
input.level,
);
Ok(success_no_data(&format!(
"Rows {}-{} grouped at level {}",
input.start, input.end, input.level
)))
}
pub fn group_columns(
store: &mut WorkbookStore,
input: GroupColumnsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let c1 =
zavora_xlsx::utility::col_from_letter(&input.start).map_err(|e| anyhow::anyhow!("{e}"))?;
let c2 =
zavora_xlsx::utility::col_from_letter(&input.end).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.group_columns(c1, c2, input.level);
Ok(success_no_data(&format!(
"Columns {}-{} grouped at level {}",
input.start, input.end, input.level
)))
}
pub fn protect_sheet(
store: &mut WorkbookStore,
input: ProtectSheetInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(ref pw) = input.password {
ws.protect_with_password(pw);
} else {
ws.protect();
}
Ok(success_no_data(&format!(
"Sheet '{}' protected",
input.sheet_name
)))
}
pub fn protect_workbook(
store: &mut WorkbookStore,
input: ProtectWorkbookInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
if let Some(ref pw) = input.password {
entry.data.protect_with_password(pw);
} else {
entry.data.protect();
}
Ok(success_no_data("Workbook protected"))
}
pub fn autofit_columns(
store: &mut WorkbookStore,
input: AutofitColumnsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.autofit()
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data("Columns autofitted"))
}
pub fn add_chart_enhanced(
store: &mut WorkbookStore,
input: AddChartEnhancedInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ct = match input.chart_type {
ChartType::Bar => zavora_xlsx::ChartType::Bar,
ChartType::Column => zavora_xlsx::ChartType::Column,
ChartType::Line => zavora_xlsx::ChartType::Line,
ChartType::Pie => zavora_xlsx::ChartType::Pie,
ChartType::Scatter => zavora_xlsx::ChartType::Scatter,
ChartType::Area => zavora_xlsx::ChartType::Area,
ChartType::Doughnut => zavora_xlsx::ChartType::Doughnut,
};
let mut chart = zavora_xlsx::Chart::new(ct);
if let Some(ref t) = input.title {
chart.set_title(t);
}
if let Some(ref x) = input.x_axis_label {
chart.set_x_axis_name(x);
}
if let Some(ref y) = input.y_axis_label {
chart.set_y_axis_name(y);
}
if let Some(ref lp) = input.legend_position {
chart.set_legend_position(match lp {
LegendPosition::Top => zavora_xlsx::LegendPosition::Top,
LegendPosition::Bottom => zavora_xlsx::LegendPosition::Bottom,
LegendPosition::Left => zavora_xlsx::LegendPosition::Left,
LegendPosition::Right => zavora_xlsx::LegendPosition::Right,
LegendPosition::None => zavora_xlsx::LegendPosition::None,
});
}
chart.set_width(input.width);
chart.set_height(input.height);
if let Some(ref ps) = input.pivot_source {
chart.set_pivot_source(&ps.pivot_table, &ps.sheet);
}
if !input.series.is_empty() {
for si in &input.series {
let s = chart.add_series();
s.set_values(&si.values);
if let Some(ref c) = si.categories {
s.set_categories(c);
}
if let Some(ref n) = si.name {
s.set_name(n);
}
if let Some(ref c) = si.color {
s.set_color(c.as_str());
}
if si.data_labels == Some(true) {
s.set_data_labels(true);
}
if si.secondary_axis == Some(true) {
s.set_secondary_axis(true);
}
if let Some(ref t) = si.trendline {
let tt = match t.as_str() {
"exponential" => zavora_xlsx::TrendlineType::Exponential,
"power" => zavora_xlsx::TrendlineType::Power,
"logarithmic" => zavora_xlsx::TrendlineType::Logarithmic,
_ => zavora_xlsx::TrendlineType::Linear,
};
s.set_trendline(tt);
}
if let Some(ref m) = si.marker {
let mt = match m.as_str() {
"circle" => zavora_xlsx::MarkerType::Circle,
"diamond" => zavora_xlsx::MarkerType::Diamond,
"square" => zavora_xlsx::MarkerType::Square,
"triangle" => zavora_xlsx::MarkerType::Triangle,
_ => zavora_xlsx::MarkerType::None,
};
s.set_marker(mt);
}
}
} else if let Some(ref dr) = input.data_range {
chart.add_series().set_values(dr);
}
let (row, col) = if let Some(ref c) = input.cell {
zavora_xlsx::utility::parse_cell_ref(c).map_err(|e| anyhow::anyhow!("{e}"))?
} else {
(0, 0)
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_chart(row, col, &chart)?;
Ok(success_no_data(&format!(
"Chart added to '{}'",
input.sheet_name
)))
}
pub fn add_pivot_table(
store: &mut WorkbookStore,
input: AddPivotTableInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let mut pt = zavora_xlsx::PivotTable::new(&input.name, &input.source_range);
for f in &input.row_fields {
pt = pt.add_row_field(f);
}
for f in &input.column_fields {
pt = pt.add_column_field(f);
}
for vf in &input.value_fields {
let agg = match vf.aggregation.as_str() {
"count" => zavora_xlsx::PivotAggregation::Count,
"average" => zavora_xlsx::PivotAggregation::Average,
"max" => zavora_xlsx::PivotAggregation::Max,
"min" => zavora_xlsx::PivotAggregation::Min,
"product" => zavora_xlsx::PivotAggregation::Product,
_ => zavora_xlsx::PivotAggregation::Sum,
};
pt = pt.add_value_field(&vf.field, agg);
}
for f in &input.filter_fields {
pt = pt.add_filter_field(f);
}
if let Some(ref s) = input.style {
pt = pt.set_style_name(s);
}
if let Some(ref l) = input.layout {
let layout = match l.as_str() {
"outline" => zavora_xlsx::PivotLayout::Outline,
"tabular" => zavora_xlsx::PivotLayout::Tabular,
_ => zavora_xlsx::PivotLayout::Compact,
};
pt = pt.set_layout(layout);
}
let (row, col) = if let Some(ref c) = input.cell {
zavora_xlsx::utility::parse_cell_ref(c).map_err(|e| anyhow::anyhow!("{e}"))?
} else {
(0, 0)
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.add_pivot_table(row, col, &pt)?;
Ok(success_no_data(&format!(
"Pivot table '{}' added",
input.name
)))
}
pub fn read_comments(
store: &mut WorkbookStore,
input: ReadCommentsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let comments: Vec<serde_json::Value> = ws.comments().iter().map(|c| {
serde_json::json!({ "cell": zavora_xlsx::utility::to_a1(c.row, c.col), "author": c.author, "text": c.text })
}).collect();
Ok(success("Comments read", comments))
}
pub fn write_rich_text(
store: &mut WorkbookStore,
input: WriteRichTextInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let mut rt = zavora_xlsx::RichText::new();
for run in &input.runs {
let mut built = zavora_xlsx::RichTextRun {
text: run.text.clone(),
bold: run.bold.unwrap_or(false),
italic: run.italic.unwrap_or(false),
font_size: run.font_size,
font_name: None,
color: None,
superscript: false,
subscript: false,
};
if let Some(ref c) = run.color {
built.color = Some(c.clone());
}
rt.runs.push(built);
}
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.write_rich_text(row, col, &rt)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Rich text written to {}",
input.cell
)))
}
fn build_format(
bold: Option<bool>,
italic: Option<bool>,
font_size: Option<f64>,
font_color: Option<&str>,
bg: Option<&str>,
nf: Option<&str>,
) -> zavora_xlsx::Format {
let mut f = zavora_xlsx::Format::new();
if bold == Some(true) {
f = f.bold();
}
if italic == Some(true) {
f = f.italic();
}
if let Some(s) = font_size {
f = f.font_size(s);
}
if let Some(c) = font_color {
f = f.font_color(c);
}
if let Some(c) = bg {
f = f.background_color(c);
}
if let Some(n) = nf {
f = f.num_format(n);
}
f
}
pub fn set_column_format(
store: &mut WorkbookStore,
input: SetColumnFormatInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col =
zavora_xlsx::utility::col_from_letter(&input.column).map_err(|e| anyhow::anyhow!("{e}"))?;
let fmt = build_format(
input.bold,
input.italic,
input.font_size,
input.font_color.as_deref(),
input.background_color.as_deref(),
input.number_format.as_deref(),
);
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_column_format(col, &fmt);
Ok(success_no_data(&format!(
"Column {} format set",
input.column
)))
}
pub fn set_row_format(
store: &mut WorkbookStore,
input: SetRowFormatInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let fmt = build_format(
input.bold,
input.italic,
input.font_size,
input.font_color.as_deref(),
input.background_color.as_deref(),
input.number_format.as_deref(),
);
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_row_format(input.row.saturating_sub(1), &fmt);
Ok(success_no_data(&format!("Row {} format set", input.row)))
}
pub fn set_column_hidden(
store: &mut WorkbookStore,
input: SetColumnHiddenInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col =
zavora_xlsx::utility::col_from_letter(&input.column).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_column_hidden(col, input.hidden);
Ok(success_no_data(&format!(
"Column {} hidden={}",
input.column, input.hidden
)))
}
pub fn set_row_hidden(
store: &mut WorkbookStore,
input: SetRowHiddenInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_row_hidden(input.row.saturating_sub(1), input.hidden);
Ok(success_no_data(&format!(
"Row {} hidden={}",
input.row, input.hidden
)))
}
pub fn set_column_range_width(
store: &mut WorkbookStore,
input: SetColumnRangeWidthInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let c1 = zavora_xlsx::utility::col_from_letter(&input.first_column)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let c2 = zavora_xlsx::utility::col_from_letter(&input.last_column)
.map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_column_range_width(c1, c2, input.width);
Ok(success_no_data(&format!(
"Columns {}:{} width set to {}",
input.first_column, input.last_column, input.width
)))
}
pub fn set_default_row_height(
store: &mut WorkbookStore,
input: SetDefaultRowHeightInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_default_row_height(input.height);
Ok(success_no_data(&format!(
"Default row height set to {}",
input.height
)))
}
pub fn set_selection(
store: &mut WorkbookStore,
input: SetSelectionInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_selection(row, col);
Ok(success_no_data(&format!("Selection set to {}", input.cell)))
}
pub fn set_autofilter(
store: &mut WorkbookStore,
input: SetAutofilterInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (r1, c1, r2, c2) =
zavora_xlsx::utility::parse_range_ref(&input.range).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_autofilter(r1, c1, r2, c2);
Ok(success_no_data(&format!(
"Autofilter set on {}",
input.range
)))
}
pub fn filter_column(
store: &mut WorkbookStore,
input: FilterColumnInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col =
zavora_xlsx::utility::col_from_letter(&input.column).map_err(|e| anyhow::anyhow!("{e}"))?;
let strs: Vec<&str> = input.values.iter().map(|s| s.as_str()).collect();
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.filter_column(col, &strs);
Ok(success_no_data(&format!(
"Filter applied to column {}",
input.column
)))
}
pub fn ignore_error(
store: &mut WorkbookStore,
input: IgnoreErrorInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.ignore_error(&input.error_type, &input.range);
Ok(success_no_data("Error ignored"))
}
pub fn set_page_breaks(
store: &mut WorkbookStore,
input: SetPageBreaksInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.set_page_breaks(&input.row_breaks, &input.col_breaks);
Ok(success_no_data("Page breaks set"))
}
pub fn unprotect_range(
store: &mut WorkbookStore,
input: UnprotectRangeInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(ref pw) = input.password {
ws.unprotect_range_with_password(&input.range, &input.title, pw);
} else {
ws.unprotect_range(&input.range, &input.title);
}
Ok(success_no_data(&format!(
"Range {} unprotected",
input.range
)))
}
pub fn write_formula(
store: &mut WorkbookStore,
input: WriteFormulaInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(result) = input.cached_result {
ws.write_formula_with_result(row, col, &input.formula, result)
.map_err(|e| anyhow::anyhow!("{e}"))?;
} else {
ws.write_formula(row, col, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
Ok(success_no_data(&format!(
"Formula written to {}",
input.cell
)))
}
pub fn write_array_formula(
store: &mut WorkbookStore,
input: WriteArrayFormulaInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (r1, c1, r2, c2) =
zavora_xlsx::utility::parse_range_ref(&input.range).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.write_array_formula(r1, c1, r2, c2, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Array formula written to {}",
input.range
)))
}
pub fn write_dynamic_formula(
store: &mut WorkbookStore,
input: WriteDynamicFormulaInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.write_dynamic_formula(row, col, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Dynamic formula written to {}",
input.cell
)))
}
pub fn write_blank(
store: &mut WorkbookStore,
input: WriteBlankInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let fmt = build_format(
input.bold,
None,
None,
None,
input.background_color.as_deref(),
input.number_format.as_deref(),
);
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.write_blank(row, col, &fmt)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Blank cell written at {}",
input.cell
)))
}
pub fn clear_cell(
store: &mut WorkbookStore,
input: ClearCellInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.clear_cell(row, col);
Ok(success_no_data(&format!("Cell {} cleared", input.cell)))
}
pub fn set_calc_mode(
store: &mut WorkbookStore,
input: SetCalcModeInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let mode = match input.mode.as_str() {
"manual" => zavora_xlsx::CalcMode::Manual,
"auto_no_table" => zavora_xlsx::CalcMode::AutoNoTable,
_ => zavora_xlsx::CalcMode::Auto,
};
entry.data.set_calc_mode(mode);
Ok(success_no_data(&format!("Calc mode set to {}", input.mode)))
}
pub fn set_properties(
store: &mut WorkbookStore,
input: SetPropertiesInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let mut props = entry.data.properties().clone();
if let Some(ref t) = input.title {
props.title = Some(t.clone());
}
if let Some(ref a) = input.author {
props.author = Some(a.clone());
}
if let Some(ref s) = input.subject {
props.subject = Some(s.clone());
}
if let Some(ref c) = input.company {
props.company = Some(c.clone());
}
if let Some(ref d) = input.description {
props.description = Some(d.clone());
}
entry.data.set_properties(props);
Ok(success_no_data("Document properties set"))
}
pub fn move_worksheet(
store: &mut WorkbookStore,
input: MoveWorksheetInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
entry
.data
.move_worksheet(idx, input.to_index)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Sheet '{}' moved to position {}",
input.sheet_name, input.to_index
)))
}
pub fn write_internal_link(
store: &mut WorkbookStore,
input: WriteInternalLinkInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.write_internal_link(row, col, &input.location, &input.display_text)
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(success_no_data(&format!(
"Internal link written at {}",
input.cell
)))
}
pub fn configure_workbook(
store: &mut WorkbookStore,
input: ConfigureWorkbookInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
if let Some(ref m) = input.calc_mode {
let mode = match m.as_str() {
"manual" => zavora_xlsx::CalcMode::Manual,
"auto_no_table" => zavora_xlsx::CalcMode::AutoNoTable,
_ => zavora_xlsx::CalcMode::Auto,
};
entry.data.set_calc_mode(mode);
}
if let Some(i) = input.active_sheet {
entry.data.set_active_sheet(i);
}
let mut props = entry.data.properties().clone();
if let Some(ref v) = input.title {
props.title = Some(v.clone());
}
if let Some(ref v) = input.author {
props.author = Some(v.clone());
}
if let Some(ref v) = input.subject {
props.subject = Some(v.clone());
}
if let Some(ref v) = input.company {
props.company = Some(v.clone());
}
if let Some(ref v) = input.description {
props.description = Some(v.clone());
}
entry.data.set_properties(props);
Ok(success_no_data("Workbook configured"))
}
pub fn modify_rows(
store: &mut WorkbookStore,
input: ModifyRowsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let row = input.at_row.saturating_sub(1);
match input.action.as_str() {
"delete" => {
ws.remove_rows(row, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
_ => {
ws.insert_rows(row, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
Ok(success_no_data(&format!(
"{} {} rows at row {}",
input.action, input.count, input.at_row
)))
}
pub fn modify_columns(
store: &mut WorkbookStore,
input: ModifyColumnsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let col = zavora_xlsx::utility::col_from_letter(&input.at_column)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.action.as_str() {
"delete" => {
ws.remove_columns(col, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
_ => {
ws.insert_columns(col, input.count)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
Ok(success_no_data(&format!(
"{} {} columns at {}",
input.action, input.count, input.at_column
)))
}
pub fn write_formula_consolidated(
store: &mut WorkbookStore,
input: WriteFormulaConsolidatedInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.formula_type.as_deref().unwrap_or("regular") {
"array" => {
let (r1, c1, r2, c2) = zavora_xlsx::utility::parse_range_ref(&input.cell)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.write_array_formula(r1, c1, r2, c2, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
"dynamic" => {
let (row, col) = zavora_xlsx::utility::parse_cell_ref(&input.cell)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.write_dynamic_formula(row, col, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
_ => {
let (row, col) = zavora_xlsx::utility::parse_cell_ref(&input.cell)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(r) = input.cached_result {
ws.write_formula_with_result(row, col, &input.formula, r)
.map_err(|e| anyhow::anyhow!("{e}"))?;
} else {
ws.write_formula(row, col, &input.formula)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
}
Ok(success_no_data(&format!(
"Formula written to {}",
input.cell
)))
}
pub fn manage_cell(
store: &mut WorkbookStore,
input: ManageCellInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.action.as_str() {
"clear" => {
ws.clear_cell(row, col);
}
_ => {
let fmt = build_format(
None,
None,
None,
None,
input.background_color.as_deref(),
input.number_format.as_deref(),
);
ws.write_blank(row, col, &fmt)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
Ok(success_no_data(&format!(
"Cell {} {}",
input.cell, input.action
)))
}
pub fn manage_comments(
store: &mut WorkbookStore,
input: ManageCommentsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
match input.action.as_str() {
"read" => {
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let comments: Vec<serde_json::Value> = ws.comments().iter().map(|c|
serde_json::json!({"cell": zavora_xlsx::utility::to_a1(c.row, c.col), "author": c.author, "text": c.text})
).collect();
Ok(success("Comments read", comments))
}
_ => {
let cell = input.cell.as_deref().unwrap_or("A1");
let text = input.text.as_deref().unwrap_or("");
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(ref a) = input.author {
ws.add_comment_with_author(row, col, text, a);
} else {
ws.add_comment(row, col, text);
}
Ok(success_no_data(&format!("Comment added at {cell}")))
}
}
}
pub fn manage_defined_names(
store: &mut WorkbookStore,
input: ManageDefinedNamesInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
match input.action.as_str() {
"list" => {
let names: Vec<serde_json::Value> = entry
.data
.defined_names()
.iter()
.map(|(n, f)| serde_json::json!({"name": n, "formula": f}))
.collect();
Ok(success("Defined names", names))
}
_ => {
let name = input.name.as_deref().unwrap_or("");
let formula = input.formula.as_deref().unwrap_or("");
entry.data.define_name(name, formula);
Ok(success_no_data(&format!("Defined name '{name}' added")))
}
}
}
pub fn add_link(store: &mut WorkbookStore, input: AddLinkInput) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.link_type.as_str() {
"internal" => {
ws.write_internal_link(
row,
col,
&input.target,
input.display_text.as_deref().unwrap_or(&input.target),
)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
_ => {
ws.write_url(
row,
col,
&input.target,
input.display_text.as_deref().unwrap_or(&input.target),
)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
Ok(success_no_data(&format!("Link added at {}", input.cell)))
}
pub fn protect_consolidated(
store: &mut WorkbookStore,
input: ProtectInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
match input.target.as_str() {
"workbook" => {
if let Some(ref pw) = input.password {
entry.data.protect_with_password(pw);
} else {
entry.data.protect();
}
Ok(success_no_data("Workbook protected"))
}
"unprotect_range" => {
let sn = input.sheet_name.as_deref().unwrap_or("Sheet1");
let idx = match find_sheet(&entry.data, sn) {
Some(i) => i,
None => return Ok(sheet_err(sn)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let range = input.range.as_deref().unwrap_or("A1:A1");
let title = input.range_title.as_deref().unwrap_or("Range");
if let Some(ref pw) = input.password {
ws.unprotect_range_with_password(range, title, pw);
} else {
ws.unprotect_range(range, title);
}
Ok(success_no_data(&format!("Range {range} unprotected")))
}
_ => {
let sn = input.sheet_name.as_deref().unwrap_or("Sheet1");
let idx = match find_sheet(&entry.data, sn) {
Some(i) => i,
None => return Ok(sheet_err(sn)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(ref pw) = input.password {
ws.protect_with_password(pw);
} else {
ws.protect();
}
Ok(success_no_data(&format!("Sheet '{sn}' protected")))
}
}
}
pub fn set_dimensions(
store: &mut WorkbookStore,
input: SetDimensionsInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.target.as_str() {
"row_height" => {
ws.set_row_height(input.row.unwrap_or(1).saturating_sub(1), input.value)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
"column_range_width" => {
let c1 =
zavora_xlsx::utility::col_from_letter(input.first_column.as_deref().unwrap_or("A"))
.map_err(|e| anyhow::anyhow!("{e}"))?;
let c2 =
zavora_xlsx::utility::col_from_letter(input.last_column.as_deref().unwrap_or("A"))
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_column_range_width(c1, c2, input.value);
}
"default_row_height" => {
ws.set_default_row_height(input.value);
}
_ => {
let col = zavora_xlsx::utility::col_from_letter(input.column.as_deref().unwrap_or("A"))
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_column_width(col, input.value)
.map_err(|e| anyhow::anyhow!("{e}"))?;
}
}
Ok(success_no_data(&format!(
"{} set to {}",
input.target, input.value
)))
}
pub fn set_visibility(
store: &mut WorkbookStore,
input: SetVisibilityInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.target.as_str() {
"row" => {
let r: u32 = input.identifier.parse().unwrap_or(1);
ws.set_row_hidden(r.saturating_sub(1), input.hidden);
}
_ => {
let col = zavora_xlsx::utility::col_from_letter(&input.identifier)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_column_hidden(col, input.hidden);
}
}
Ok(success_no_data(&format!(
"{} {} hidden={}",
input.target, input.identifier, input.hidden
)))
}
pub fn set_row_column_format(
store: &mut WorkbookStore,
input: SetRowColumnFormatInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let fmt = build_format(
input.bold,
input.italic,
input.font_size,
input.font_color.as_deref(),
input.background_color.as_deref(),
input.number_format.as_deref(),
);
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.target.as_str() {
"row" => {
let r: u32 = input.identifier.parse().unwrap_or(1);
ws.set_row_format(r.saturating_sub(1), &fmt);
}
_ => {
let col = zavora_xlsx::utility::col_from_letter(&input.identifier)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_column_format(col, &fmt);
}
}
Ok(success_no_data(&format!(
"{} {} format set",
input.target, input.identifier
)))
}
pub fn group_consolidated(
store: &mut WorkbookStore,
input: GroupInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
match input.target.as_str() {
"columns" => {
let c1 = zavora_xlsx::utility::col_from_letter(&input.start)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let c2 = zavora_xlsx::utility::col_from_letter(&input.end)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.group_columns(c1, c2, input.level);
}
_ => {
let s: u32 = input.start.parse().unwrap_or(1);
let e: u32 = input.end.parse().unwrap_or(1);
ws.group_rows(s.saturating_sub(1), e.saturating_sub(1), input.level);
}
}
Ok(success_no_data(&format!(
"{} {}-{} grouped at level {}",
input.target, input.start, input.end, input.level
)))
}
pub fn manage_autofilter(
store: &mut WorkbookStore,
input: ManageAutofilterInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let (r1, c1, r2, c2) =
zavora_xlsx::utility::parse_range_ref(&input.range).map_err(|e| anyhow::anyhow!("{e}"))?;
let ws = entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?;
ws.set_autofilter(r1, c1, r2, c2);
if let (Some(ref fc), Some(ref fv)) = (input.filter_column, input.filter_values) {
let col = zavora_xlsx::utility::col_from_letter(fc).map_err(|e| anyhow::anyhow!("{e}"))?;
let strs: Vec<&str> = fv.iter().map(|s| s.as_str()).collect();
ws.filter_column(col, &strs);
}
Ok(success_no_data(&format!(
"Autofilter set on {}",
input.range
)))
}
fn parse_hex_color(hex: &str) -> [u8; 3] {
let s = hex.trim_start_matches('#');
if s.len() == 6 {
let r = u8::from_str_radix(&s[0..2], 16).unwrap_or(0);
let g = u8::from_str_radix(&s[2..4], 16).unwrap_or(0);
let b = u8::from_str_radix(&s[4..6], 16).unwrap_or(0);
[r, g, b]
} else {
[0, 0, 0]
}
}
pub fn add_waterfall_chart(
store: &mut WorkbookStore,
input: AddWaterfallChartInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let mut chart = zavora_xlsx::WaterfallChart::new();
if let Some(ref t) = input.title {
chart.set_title(t);
}
if let Some(ref n) = input.series_name {
chart.set_series_name(n);
}
chart.set_width(input.width);
chart.set_height(input.height);
for pt in &input.points {
let point_type = match pt.point_type {
crate::types::enums::WaterfallPointKind::Increase => {
zavora_xlsx::WaterfallPointType::Increase
}
crate::types::enums::WaterfallPointKind::Decrease => {
zavora_xlsx::WaterfallPointType::Decrease
}
crate::types::enums::WaterfallPointKind::Total => {
zavora_xlsx::WaterfallPointType::Total
}
};
chart.add_point(&pt.category, pt.value, point_type);
}
let (row, col) = if let Some(ref c) = input.cell {
zavora_xlsx::utility::parse_cell_ref(c).map_err(|e| anyhow::anyhow!("{e}"))?
} else {
(0, 0)
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_waterfall(row, col, &chart)?;
Ok(success_no_data(&format!(
"Waterfall chart added to '{}'",
input.sheet_name
)))
}
pub fn add_funnel_chart(
store: &mut WorkbookStore,
input: AddFunnelChartInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let mut chart = zavora_xlsx::FunnelChart::new();
if let Some(ref t) = input.title {
chart.set_title(t);
}
if let Some(ref n) = input.series_name {
chart.set_series_name(n);
}
chart.set_width(input.width);
chart.set_height(input.height);
for pt in &input.points {
chart.add_point(&pt.category, pt.value);
}
let (row, col) = if let Some(ref c) = input.cell {
zavora_xlsx::utility::parse_cell_ref(c).map_err(|e| anyhow::anyhow!("{e}"))?
} else {
(0, 0)
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_funnel(row, col, &chart)?;
Ok(success_no_data(&format!(
"Funnel chart added to '{}'",
input.sheet_name
)))
}
pub fn add_treemap_chart(
store: &mut WorkbookStore,
input: AddTreemapChartInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let mut chart = zavora_xlsx::TreemapChart::new();
if let Some(ref t) = input.title {
chart.set_title(t);
}
if let Some(ref n) = input.series_name {
chart.set_series_name(n);
}
chart.set_width(input.width);
chart.set_height(input.height);
for pt in &input.points {
if let Some(ref c) = pt.color {
chart.add_point_with_color(&pt.category, pt.value, c.as_str());
} else {
chart.add_point(&pt.category, pt.value);
}
}
let (row, col) = if let Some(ref c) = input.cell {
zavora_xlsx::utility::parse_cell_ref(c).map_err(|e| anyhow::anyhow!("{e}"))?
} else {
(0, 0)
};
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert_treemap(row, col, &chart)?;
Ok(success_no_data(&format!(
"Treemap chart added to '{}'",
input.sheet_name
)))
}
pub fn add_shape(store: &mut WorkbookStore, input: AddShapeInput) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let idx = match find_sheet(&entry.data, &input.sheet_name) {
Some(i) => i,
None => return Ok(sheet_err(&input.sheet_name)),
};
let st = match input.shape_type {
crate::types::enums::ShapeKind::Rectangle => zavora_xlsx::ShapeType::Rectangle,
crate::types::enums::ShapeKind::RoundedRectangle => {
zavora_xlsx::ShapeType::RoundedRectangle
}
crate::types::enums::ShapeKind::Ellipse => zavora_xlsx::ShapeType::Ellipse,
crate::types::enums::ShapeKind::Triangle => zavora_xlsx::ShapeType::Triangle,
crate::types::enums::ShapeKind::Diamond => zavora_xlsx::ShapeType::Diamond,
crate::types::enums::ShapeKind::Arrow => zavora_xlsx::ShapeType::Arrow,
crate::types::enums::ShapeKind::Callout => zavora_xlsx::ShapeType::Callout,
crate::types::enums::ShapeKind::TextBox => zavora_xlsx::ShapeType::TextBox,
};
let mut shape = zavora_xlsx::Shape::new(st, input.width, input.height);
if let Some(ref t) = input.text {
shape = shape.text(t);
}
if let Some(ref c) = input.fill_color {
shape = shape.fill_color(parse_hex_color(c));
}
if let Some(ref c) = input.outline_color {
shape = shape.outline_color(parse_hex_color(c));
}
if let Some(w) = input.outline_width {
shape = shape.outline_width(w);
}
if let Some(s) = input.font_size {
shape = shape.font_size(s);
}
if input.bold == Some(true) {
shape = shape.bold();
}
let (row, col) =
zavora_xlsx::utility::parse_cell_ref(&input.cell).map_err(|e| anyhow::anyhow!("{e}"))?;
entry
.data
.worksheet(idx)
.map_err(|e| anyhow::anyhow!("{e}"))?
.add_shape(row, col, &shape)?;
Ok(success_no_data(&format!("Shape added at {}", input.cell)))
}
pub fn set_doc_properties(
store: &mut WorkbookStore,
input: SetDocPropertiesInput,
) -> Result<String, anyhow::Error> {
let entry = match store.get_mut(&input.workbook_id) {
Some(e) => e,
None => return Ok(workbook_not_found(store, &input.workbook_id)),
};
let mut props = zavora_xlsx::DocProperties::new();
if let Some(ref v) = input.title {
props = props.title(v);
}
if let Some(ref v) = input.author {
props = props.author(v);
}
if let Some(ref v) = input.subject {
props = props.subject(v);
}
if let Some(ref v) = input.description {
props = props.description(v);
}
if let Some(ref v) = input.keywords {
props = props.keywords(v);
}
if let Some(ref v) = input.category {
props = props.category(v);
}
if let Some(ref v) = input.company {
props = props.company(v);
}
entry.data.set_properties(props);
Ok(success_no_data("Document properties set"))
}